docs: documentation standardisée des 14 sources (générateur + fiches)
scripts/gen_connector_docs.py : générateur 100 % programmatique et rejouable qui croise data/sources.json (famille, palier, accès, signal d identité), l introspection statique (ast) de creaka/connectors/*.py et la BD live (creators/doc JSON, accounts par plateforme, sync_log : cadence, alertes). Écrit docs/connecteurs/INDEX.md + une fiche par source (sections fixes). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
16 changed files +1,594 −0
added
docs/connecteurs/INDEX.md
+22 −0
@@ -0,0 +1,22 @@ | ||
| 1 | +# Créa-Ka — Index des connecteurs | |
| 2 | + | |
| 3 | +_Généré automatiquement par `scripts/gen_connector_docs.py` le 2026-08-18 03:30 — ne pas éditer à la main, régénérer._ | |
| 4 | + | |
| 5 | +**14 sources au registre** · **5664 créateurs** et **6527 comptes** en BD. | |
| 6 | + | |
| 7 | +| Source | Famille | Palier | Type d'accès | Backend | Découverts | Comptes (plateforme) | État | Dernier passage | | |
| 8 | +|---|---|---|---|---|---|---|---|---| | |
| 9 | +| [`listes-medias`](listes-medias.md) | decouverte | P3 | dataset local compilé (data/seed) | direct | 310 | — | actif | 2026-08-18T06:23… | | |
| 10 | +| [`wikidata-qc`](wikidata-qc.md) | decouverte | P3 | API SPARQL publique (Wikidata) | direct | 73 | — | actif | 2026-08-18T06:27… | | |
| 11 | +| [`onlyqueb`](onlyqueb.md) | decouverte | P3 | sitemap XML + JSON-LD des pages profil (S… | direct | 253 | 248 (onlyfans) | actif | 2026-08-18T06:27… | | |
| 12 | +| [`youtube-recherche`](youtube-recherche.md) | decouverte | P3 | API JSON | direct | 4595 | 4768 (youtube) | actif | 2026-08-18T07:22… | | |
| 13 | +| [`balados-itunes`](balados-itunes.md) | decouverte | P2 | API iTunes Search/Lookup (publique) | direct | 433 | 434 (podcast) | actif | 2026-08-18T06:23… | | |
| 14 | +| [`instagram-profil`](instagram-profil.md) | enrichissement | P4 | pages HTML publiques | Scrapfly | 0 | 460 (instagram) | actif | 2026-08-17T23:23… | | |
| 15 | +| [`tiktok-profil`](tiktok-profil.md) | enrichissement | P4 | pages HTML publiques | Scrapfly | 0 | 197 (tiktok) | actif | 2026-08-18T00:31… | | |
| 16 | +| [`youtube`](youtube.md) | enrichissement | P2 | API JSON | direct | 0 | 4768 (youtube) | actif | 2026-08-18T01:06… | | |
| 17 | +| [`twitch`](twitch.md) | enrichissement | P2 | API JSON | direct | 0 | 26 (twitch) | actif | 2026-08-18T01:06… | | |
| 18 | +| [`link-in-bio`](link-in-bio.md) | enrichissement | P1 | pages HTML publiques | Firecrawl | 0 | — | actif | 2026-08-18T06:20… | | |
| 19 | +| [`balados-rss`](balados-rss.md) | enrichissement | P1 | flux RSS publics | direct | 0 | 434 (podcast) | actif | 2026-08-18T06:17… | | |
| 20 | +| [`podcastindex`](podcastindex.md) | enrichissement | P2 | API JSON | direct | 0 | 434 (podcast) | prêt | — | | |
| 21 | +| [`x-profil`](x-profil.md) | enrichissement | P3 | pages HTML publiques | direct | 0 | 186 (x) | actif | 2026-08-18T06:15… | | |
| 22 | +| [`bio-liens`](bio-liens.md) | enrichissement | P1 | pages HTML publiques | direct | 0 | — | actif | 2026-08-18T06:17… | | |
added
docs/connecteurs/balados-itunes.md
+87 −0
@@ -0,0 +1,87 @@ | ||
| 1 | +# `balados-itunes` — connecteur decouverte (palier 2) | |
| 2 | + | |
| 3 | +_Fiche générée automatiquement par `scripts/gen_connector_docs.py` le 2026-08-18 03:30 — ne pas éditer à la main, régénérer._ | |
| 4 | + | |
| 5 | +**État : actif** · Famille : decouverte (discovery) · Backend : direct · Créateurs découverts : 433 · touchés : 433 | |
| 6 | + | |
| 7 | +## Description de la source | |
| 8 | + | |
| 9 | +Connecteur DÉCOUVERTE — balados québécois via l'API de recherche iTunes/Apple Podcasts (OFFICIELLE, gratuite, sans clé). Palier 2-3 (§9). Filtre conservateur : marqueur québécois requis dans le nom du balado ou de l'artiste. | |
| 10 | + | |
| 11 | +- **Notes (registre)** : podcasteurs québécois : nom, artiste, pochette, genre, nb d'épisodes, flux RSS ; marqueur QC requis | |
| 12 | +- **Signal d'identité** : profil_source (0.98) (colonne `accounts.signal` / `confidence`) | |
| 13 | +- **Module** : `creaka/connectors/balados_itunes.py` — classe `BaladosItunesConnector` (`kind = "discovery"`) | |
| 14 | + | |
| 15 | +## Accès | |
| 16 | + | |
| 17 | +- **Accès (registre)** : API de recherche iTunes/Apple Podcasts (officielle, gratuite, sans clé) | |
| 18 | +- **Type d'accès (code)** : API iTunes Search/Lookup (publique) | |
| 19 | +- **Endpoint de base** : https://itunes.apple.com/search | |
| 20 | +- **URLs du module** : https://itunes.apple.com/search | |
| 21 | +- **Pagination** : réponse unique (pas de pagination) | |
| 22 | +- **Backend anti-bot / rendu** : requests direct (session UA CreaKaBot, throttling poli) | |
| 23 | +- **Politesse** : 1.5 s entre requêtes, timeout 30 s, UA `CreaKaBot/1.0 (+https://www.crea-ka.com/bot)` | |
| 24 | +- **Authentification** : clé(s) API requise(s) — voir statut/registre | |
| 25 | + | |
| 26 | +## Champs récupérés → schéma cible | |
| 27 | + | |
| 28 | +Source de **découverte** : produit des fiches `Creator` (tables `creators` + `accounts`). Complétude mesurée sur les 433 créateurs découverts par cette source (champ `doc.source`) ; exemple tiré d'une fiche réelle. | |
| 29 | + | |
| 30 | +| Champ du doc créateur | Contenu | Renseigné | Exemple réel | | |
| 31 | +|---|---|---|---| | |
| 32 | +| `display_name` | Nom public | 100 % | Relève et Repreneuriat au Québec - Le Podcast | | |
| 33 | +| `bio` | Bio | 97 % | Le balado qui met l’humain au cœur des transferts d’entreprise. Dans les années à venir, … | | |
| 34 | +| `region` | Région | 0 % | — | | |
| 35 | +| `city` | Ville | 0 % | — | | |
| 36 | +| `niches` | Niches (liste) | 100 % | actualite-opinion, arts, finance-affaires | | |
| 37 | +| `languages` | Langues (liste) | 100 % | fr | | |
| 38 | +| `creator_type` | Type de créateur | 100 % | podcasteur | | |
| 39 | +| `primary_platform` | Plateforme principale | 100 % | youtube | | |
| 40 | +| `platforms` | Comptes sociaux (liste) | 100 % | youtube:@releveetrepreneuriat, podcast:@1889244567 | | |
| 41 | +| `avatar_url` | Avatar | 100 % | https://is1-ssl.mzstatic.com/image/thumb/Podcasts211/v4/66/d3/96/66d3962a-1201-b271-25bb-… | | |
| 42 | +| `total_reach` | Portée totale (abonnés cumulés) | 0 % | 176 | | |
| 43 | +| `link_in_bio_url` | Lien-en-bio | 0 % | — | | |
| 44 | + | |
| 45 | +Cible d'**enrichissement** : colonnes de la table `accounts` pour la/les plateforme(s) `podcast` (434 comptes en BD, confiance moyenne 0.98). | |
| 46 | + | |
| 47 | +| Colonne `accounts` | Contenu | Renseignée | Exemple réel | | |
| 48 | +|---|---|---|---| | |
| 49 | +| `handle` | Handle | 100 % | — | | |
| 50 | +| `url` | URL du profil | 100 % | — | | |
| 51 | +| `followers` | Abonnés | 0 % | — | | |
| 52 | +| `verified` | Badge vérifié | 0 % | — | | |
| 53 | +| `confidence` | Confiance d'identité | 100 % | — | | |
| 54 | +| `signal` | Signal d'identité | 100 % | — | | |
| 55 | +| `metrics` | Métriques détaillées (JSON) | 100 % | — | | |
| 56 | +| `last_checked` | Dernière vérification | 100 % | — | | |
| 57 | + | |
| 58 | +## Fréquence & budget | |
| 59 | + | |
| 60 | +- **Cadence déclarée (registre)** : quotidienne (watch) | |
| 61 | +- **Cadence observée** (médiane sync_log) : ≈ 24 min | |
| 62 | +- **Dernier passage** : 2026-08-18T06:23:00Z — 433 créateurs, 433 comptes, +0 / ~433, 0 erreur(s), 131 s | |
| 63 | +- **Throttling** : 1.5 s entre requêtes (`request_delay`) | |
| 64 | + | |
| 65 | +## Volumétrie & complétude | |
| 66 | + | |
| 67 | +- **Créateurs découverts par la source** (`doc.source`) : 433 · **fiches touchées** (`source_ids`) : 433 | |
| 68 | +- **Complétude clé (découverts)** : niches 100 % · avatar 100 % · portée 0 % · région 0 % | |
| 69 | +- **Comptes `podcast` en BD** : 434 — abonnés 0 % · métriques 100 % · dernière vérification 2026-08-18T06:23:00Z | |
| 70 | +- **Runs journalisés (60 derniers)** : 7, dont 0 avec erreurs | |
| 71 | + | |
| 72 | +## Erreurs connues & dépannage | |
| 73 | + | |
| 74 | +Aucune erreur ni alerte dans les 60 derniers runs journalisés. | |
| 75 | + | |
| 76 | +Rejouer la source seule : `python3 run.py sync balados-itunes` · vérifier `sync_log` (`SELECT * FROM sync_log WHERE source='balados-itunes' ORDER BY ts DESC LIMIT 5;`). | |
| 77 | + | |
| 78 | +## Licence, attribution & conditions | |
| 79 | + | |
| 80 | +- **Cadre d'accès (registre)** : API de recherche iTunes/Apple Podcasts (officielle, gratuite, sans clé) | |
| 81 | +- **API publique** utilisée selon ses conditions (pas de clé détournée, throttling poli) ; données limitées aux profils publics. | |
| 82 | +- Retrait sur demande : contact@spboucher.ai (opt-out honoré à la prochaine ingestion). | |
| 83 | + | |
| 84 | +## Historique | |
| 85 | + | |
| 86 | +- 2026-08-17 — plus ancien passage journalisé dans `sync_log` (fenêtre des 60 derniers). | |
| 87 | +- 2026-08-18 — vague d'enrichissement : standardisation de la documentation des connecteurs (fiche générée par `scripts/gen_connector_docs.py`). | |
added
docs/connecteurs/balados-rss.md
+73 −0
@@ -0,0 +1,73 @@ | ||
| 1 | +# `balados-rss` — connecteur enrichissement (palier 1) | |
| 2 | + | |
| 3 | +_Fiche générée automatiquement par `scripts/gen_connector_docs.py` le 2026-08-18 03:30 — ne pas éditer à la main, régénérer._ | |
| 4 | + | |
| 5 | +**État : actif** · Famille : enrichissement (enrichment) · Backend : direct · Créateurs découverts : 0 · touchés : 0 | |
| 6 | + | |
| 7 | +## Description de la source | |
| 8 | + | |
| 9 | +Connecteur ENRICHISSEMENT « balados-rss » — lecture directe des flux RSS auto-déclarés des balados (AUCUNE clé requise). Palier 1 (§9). Cap 150 flux/passage, re-visite 7 j, timeout court, fetch parallèle poli (les flux vivent presque tous sur des hôtes différents). | |
| 10 | + | |
| 11 | +- **Notes (registre)** : nb d'épisodes réels, date du dernier épisode (détection dormant >12 mois), pochette → avatar, description → bio, catégories iTunes → niches, <link> → compte site-web/plateforme. Substitut sans clé de Podcast Index. | |
| 12 | +- **Signal d'identité** : cross_link (0.90) pour le lien officiel <link> du flux (colonne `accounts.signal` / `confidence`) | |
| 13 | +- **Module** : `creaka/connectors/balados_rss.py` — classe `BaladosRssConnector` (`kind = "enrichment"`) | |
| 14 | + | |
| 15 | +## Accès | |
| 16 | + | |
| 17 | +- **Accès (registre)** : requêtes directes (flux RSS auto-déclarés des balados — AUCUNE clé) ; cap 150 flux/passage, re-visite 7 j, timeout court, fetch parallèle poli | |
| 18 | +- **Type d'accès (code)** : flux RSS publics | |
| 19 | +- **Endpoint de base** : http://www.itunes.com/dtds/podcast-1.0.dtd} | |
| 20 | +- **URLs du module** : http://www.itunes.com/dtds/podcast-1.0.dtd} | |
| 21 | +- **Pagination** : réponse unique (pas de pagination) | |
| 22 | +- **Backend anti-bot / rendu** : requests direct (session UA CreaKaBot, throttling poli) | |
| 23 | +- **Politesse** : 0.2 s entre requêtes, timeout 12 s, UA `CreaKaBot/1.0 (+https://www.crea-ka.com/bot)` | |
| 24 | +- **Authentification** : clé(s) API requise(s) — voir statut/registre | |
| 25 | + | |
| 26 | +## Champs récupérés → schéma cible | |
| 27 | + | |
| 28 | +Cible d'**enrichissement** : colonnes de la table `accounts` pour la/les plateforme(s) `podcast` (434 comptes en BD, confiance moyenne 0.98). | |
| 29 | + | |
| 30 | +| Colonne `accounts` | Contenu | Renseignée | Exemple réel | | |
| 31 | +|---|---|---|---| | |
| 32 | +| `handle` | Handle | 100 % | — | | |
| 33 | +| `url` | URL du profil | 100 % | — | | |
| 34 | +| `followers` | Abonnés | 0 % | — | | |
| 35 | +| `verified` | Badge vérifié | 0 % | — | | |
| 36 | +| `confidence` | Confiance d'identité | 100 % | — | | |
| 37 | +| `signal` | Signal d'identité | 100 % | — | | |
| 38 | +| `metrics` | Métriques détaillées (JSON) | 100 % | — | | |
| 39 | +| `last_checked` | Dernière vérification | 100 % | — | | |
| 40 | + | |
| 41 | +## Fréquence & budget | |
| 42 | + | |
| 43 | +- **Cadence déclarée (registre)** : quotidienne (watch) — rotation complète des 433 balados en ~3 jours, puis hebdomadaire | |
| 44 | +- **Cadence observée** (médiane sync_log) : ≈ 17 min | |
| 45 | +- **Dernier passage** : 2026-08-18T06:17:19Z — 8 créateurs, 36 comptes, +0 / ~8, 5 erreur(s), 12 s | |
| 46 | +- **Throttling** : 0.2 s entre requêtes (`request_delay`) | |
| 47 | + | |
| 48 | +## Volumétrie & complétude | |
| 49 | + | |
| 50 | +- **Créateurs découverts par la source** (`doc.source`) : 0 · **fiches touchées** (`source_ids`) : 0 | |
| 51 | +- **Comptes `podcast` en BD** : 434 — abonnés 0 % · métriques 100 % · dernière vérification 2026-08-18T06:23:00Z | |
| 52 | +- **Runs journalisés (60 derniers)** : 4, dont 3 avec erreurs | |
| 53 | + | |
| 54 | +## Erreurs connues & dépannage | |
| 55 | + | |
| 56 | +| Passage | Erreurs | Alerte (sync_log) | | |
| 57 | +|---|---|---| | |
| 58 | +| 2026-08-18T06:17:19Z | 5 | — | | |
| 59 | +| 2026-08-18T06:17:07Z | 5 | — | | |
| 60 | +| 2026-08-18T06:16:42Z | 5 | — | | |
| 61 | + | |
| 62 | +Rejouer la source seule : `python3 run.py sync balados-rss` · vérifier `sync_log` (`SELECT * FROM sync_log WHERE source='balados-rss' ORDER BY ts DESC LIMIT 5;`). | |
| 63 | + | |
| 64 | +## Licence, attribution & conditions | |
| 65 | + | |
| 66 | +- **Cadre d'accès (registre)** : requêtes directes (flux RSS auto-déclarés des balados — AUCUNE clé) ; cap 150 flux/passage, re-visite 7 j, timeout court, fetch parallèle poli | |
| 67 | +- **Profils publics uniquement** : UA identifiable `CreaKaBot/1.0 (+https://www.crea-ka.com/bot; contact@spboucher.ai)`, throttling poli, respect des opt-out (`data/optout.json`) et du volet éthique (`creaka/ethics.py` — mineurs exclus via `is_minor`). | |
| 68 | +- Retrait sur demande : contact@spboucher.ai (opt-out honoré à la prochaine ingestion). | |
| 69 | + | |
| 70 | +## Historique | |
| 71 | + | |
| 72 | +- 2026-08-18 — plus ancien passage journalisé dans `sync_log` (fenêtre des 60 derniers). | |
| 73 | +- 2026-08-18 — vague d'enrichissement : standardisation de la documentation des connecteurs (fiche générée par `scripts/gen_connector_docs.py`). | |
added
docs/connecteurs/bio-liens.md
+57 −0
@@ -0,0 +1,57 @@ | ||
| 1 | +# `bio-liens` — connecteur enrichissement (palier 1) | |
| 2 | + | |
| 3 | +_Fiche générée automatiquement par `scripts/gen_connector_docs.py` le 2026-08-18 03:30 — ne pas éditer à la main, régénérer._ | |
| 4 | + | |
| 5 | +**État : actif** · Famille : enrichissement (enrichment) · Backend : direct · Créateurs découverts : 0 · touchés : 0 | |
| 6 | + | |
| 7 | +## Description de la source | |
| 8 | + | |
| 9 | +Connecteur ENRICHISSEMENT « bio-liens » — découverte croisée inter-plateformes SANS AUCUNE requête : liaison conservatrice à partir des URLs COMPLÈTES déjà stockées dans les bios publiques. Palier 1 (§9) — passe en dernier (après link-in-bio). | |
| 10 | + | |
| 11 | +- **Notes (registre)** : passe conservatrice §12 : patrons sûrs seulement, cap 5 nouveaux comptes par fiche | |
| 12 | +- **Signal d'identité** : cross_link (0.90) — le créateur pointe lui-même vers son autre compte (colonne `accounts.signal` / `confidence`) | |
| 13 | +- **Module** : `creaka/connectors/bio_liens.py` — classe `BioLiensConnector` (`kind = "enrichment"`) | |
| 14 | + | |
| 15 | +## Accès | |
| 16 | + | |
| 17 | +- **Accès (registre)** : AUCUNE requête — liaison croisée à partir des URLs COMPLÈTES déjà stockées dans les bios publiques (jamais de « @handle » textuel ambigu) | |
| 18 | +- **Type d'accès (code)** : pages HTML publiques | |
| 19 | +- **Endpoint de base** : — | |
| 20 | +- **Pagination** : réponse unique (pas de pagination) | |
| 21 | +- **Backend anti-bot / rendu** : requests direct | |
| 22 | +- **Politesse** : 0.0 s entre requêtes, timeout 30 s, UA `CreaKaBot/1.0 (+https://www.crea-ka.com/bot)` | |
| 23 | +- **Authentification** : aucune — contenu public / API anonyme | |
| 24 | + | |
| 25 | +## Champs récupérés → schéma cible | |
| 26 | + | |
| 27 | +Aucune donnée attribuable à cette source en BD pour l'instant (clés manquantes ou source en attente) — schéma cible : `creators` + `accounts`. | |
| 28 | + | |
| 29 | +## Fréquence & budget | |
| 30 | + | |
| 31 | +- **Cadence déclarée (registre)** : quotidienne (watch), en dernier du pipeline | |
| 32 | +- **Cadence observée** (médiane sync_log) : — | |
| 33 | +- **Dernier passage** : 2026-08-18T06:17:45Z — 26 créateurs, 63 comptes, +0 / ~26, 0 erreur(s), 0 s | |
| 34 | +- **Caps / budgets du module** : `MAX_NEW_PER_CREATOR` = 5 | |
| 35 | +- **Throttling** : 0.0 s entre requêtes (`request_delay`) | |
| 36 | + | |
| 37 | +## Volumétrie & complétude | |
| 38 | + | |
| 39 | +- **Créateurs découverts par la source** (`doc.source`) : 0 · **fiches touchées** (`source_ids`) : 0 | |
| 40 | +- **Runs journalisés (60 derniers)** : 1, dont 0 avec erreurs | |
| 41 | + | |
| 42 | +## Erreurs connues & dépannage | |
| 43 | + | |
| 44 | +Aucune erreur ni alerte dans les 60 derniers runs journalisés. | |
| 45 | + | |
| 46 | +Rejouer la source seule : `python3 run.py sync bio-liens` · vérifier `sync_log` (`SELECT * FROM sync_log WHERE source='bio-liens' ORDER BY ts DESC LIMIT 5;`). | |
| 47 | + | |
| 48 | +## Licence, attribution & conditions | |
| 49 | + | |
| 50 | +- **Cadre d'accès (registre)** : AUCUNE requête — liaison croisée à partir des URLs COMPLÈTES déjà stockées dans les bios publiques (jamais de « @handle » textuel ambigu) | |
| 51 | +- **Profils publics uniquement** : UA identifiable `CreaKaBot/1.0 (+https://www.crea-ka.com/bot; contact@spboucher.ai)`, throttling poli, respect des opt-out (`data/optout.json`) et du volet éthique (`creaka/ethics.py` — mineurs exclus via `is_minor`). | |
| 52 | +- Retrait sur demande : contact@spboucher.ai (opt-out honoré à la prochaine ingestion). | |
| 53 | + | |
| 54 | +## Historique | |
| 55 | + | |
| 56 | +- 2026-08-18 — plus ancien passage journalisé dans `sync_log` (fenêtre des 60 derniers). | |
| 57 | +- 2026-08-18 — vague d'enrichissement : standardisation de la documentation des connecteurs (fiche générée par `scripts/gen_connector_docs.py`). | |
added
docs/connecteurs/instagram-profil.md
+69 −0
@@ -0,0 +1,69 @@ | ||
| 1 | +# `instagram-profil` — connecteur enrichissement (palier 4) | |
| 2 | + | |
| 3 | +_Fiche générée automatiquement par `scripts/gen_connector_docs.py` le 2026-08-18 03:30 — ne pas éditer à la main, régénérer._ | |
| 4 | + | |
| 5 | +**État : actif** · Famille : enrichissement (enrichment) · Backend : Scrapfly · Créateurs découverts : 0 · touchés : 0 | |
| 6 | + | |
| 7 | +## Description de la source | |
| 8 | + | |
| 9 | +Connecteur ENRICHISSEMENT Instagram — profil public par créateur : abonnés, badge vérifié, bio, liens de bio (→ découverte croisée des autres plateformes + page link-in-bio). Mode d'accès : Scrapfly (plateforme hostile au scraping, pas d'API publique de lecture) — palier 4 du catalogue (§9), CGU/rate-limit respectés (§15). | |
| 10 | + | |
| 11 | +- **Notes (registre)** : abonnés, badge, bio, liens de bio → link_in_bio_url + comptes croisés | |
| 12 | +- **Signal d'identité** : cross_link (0.90) pour les liens de bio découverts (colonne `accounts.signal` / `confidence`) | |
| 13 | +- **Module** : `creaka/connectors/instagram.py` — classe `InstagramConnector` (`kind = "enrichment"`) | |
| 14 | + | |
| 15 | +## Accès | |
| 16 | + | |
| 17 | +- **Accès (registre)** : Scrapfly (profil public web_profile_info — pas d'API publique de lecture) | |
| 18 | +- **Type d'accès (code)** : pages HTML publiques | |
| 19 | +- **Endpoint de base** : https://i.instagram.com/api/v1/users/web_profile_info/ | |
| 20 | +- **URLs du module** : https://i.instagram.com/api/v1/users/web_profile_info/ | |
| 21 | +- **Pagination** : réponse unique (pas de pagination) | |
| 22 | +- **Backend anti-bot / rendu** : Scrapfly (asp + render_js — contournement anti-bot) | |
| 23 | +- **Politesse** : 1.5 s entre requêtes, timeout 30 s, UA `CreaKaBot/1.0 (+https://www.crea-ka.com/bot)` | |
| 24 | +- **Authentification** : aucune — contenu public / API anonyme | |
| 25 | + | |
| 26 | +## Champs récupérés → schéma cible | |
| 27 | + | |
| 28 | +Cible d'**enrichissement** : colonnes de la table `accounts` pour la/les plateforme(s) `instagram` (460 comptes en BD, confiance moyenne 0.88). | |
| 29 | + | |
| 30 | +| Colonne `accounts` | Contenu | Renseignée | Exemple réel | | |
| 31 | +|---|---|---|---| | |
| 32 | +| `handle` | Handle | 100 % | catherinepaiz | | |
| 33 | +| `url` | URL du profil | 100 % | https://www.instagram.com/catherinepaiz/ | | |
| 34 | +| `followers` | Abonnés | 72 % | 7023891 | | |
| 35 | +| `verified` | Badge vérifié | 59 % | 1 | | |
| 36 | +| `confidence` | Confiance d'identité | 100 % | 0.85 | | |
| 37 | +| `signal` | Signal d'identité | 100 % | base_publique | | |
| 38 | +| `metrics` | Métriques détaillées (JSON) | 59 % | {"following": 248, "posts": 667, "is_business": false} | | |
| 39 | +| `last_checked` | Dernière vérification | 100 % | 2026-08-18T06:27:51Z | | |
| 40 | + | |
| 41 | +## Fréquence & budget | |
| 42 | + | |
| 43 | +- **Cadence déclarée (registre)** : hebdomadaire (gros créateurs) / mensuelle | |
| 44 | +- **Cadence observée** (médiane sync_log) : ≈ 87 min | |
| 45 | +- **Dernier passage** : 2026-08-17T23:23:45Z — 271 créateurs, 542 comptes, +0 / ~271, 0 erreur(s), 0 s | |
| 46 | +- **Throttling** : 1.5 s entre requêtes (`request_delay`) | |
| 47 | + | |
| 48 | +## Volumétrie & complétude | |
| 49 | + | |
| 50 | +- **Créateurs découverts par la source** (`doc.source`) : 0 · **fiches touchées** (`source_ids`) : 0 | |
| 51 | +- **Comptes `instagram` en BD** : 460 — abonnés 72 % · métriques 59 % · dernière vérification 2026-08-18T06:27:51Z | |
| 52 | +- **Runs journalisés (60 derniers)** : 3, dont 0 avec erreurs | |
| 53 | + | |
| 54 | +## Erreurs connues & dépannage | |
| 55 | + | |
| 56 | +Aucune erreur ni alerte dans les 60 derniers runs journalisés. | |
| 57 | + | |
| 58 | +Rejouer la source seule : `python3 run.py sync instagram-profil` · vérifier `sync_log` (`SELECT * FROM sync_log WHERE source='instagram-profil' ORDER BY ts DESC LIMIT 5;`). | |
| 59 | + | |
| 60 | +## Licence, attribution & conditions | |
| 61 | + | |
| 62 | +- **Cadre d'accès (registre)** : Scrapfly (profil public web_profile_info — pas d'API publique de lecture) | |
| 63 | +- **API publique** utilisée selon ses conditions (pas de clé détournée, throttling poli) ; données limitées aux profils publics. | |
| 64 | +- Retrait sur demande : contact@spboucher.ai (opt-out honoré à la prochaine ingestion). | |
| 65 | + | |
| 66 | +## Historique | |
| 67 | + | |
| 68 | +- 2026-08-17 — plus ancien passage journalisé dans `sync_log` (fenêtre des 60 derniers). | |
| 69 | +- 2026-08-18 — vague d'enrichissement : standardisation de la documentation des connecteurs (fiche générée par `scripts/gen_connector_docs.py`). | |
added
docs/connecteurs/link-in-bio.md
+56 −0
@@ -0,0 +1,56 @@ | ||
| 1 | +# `link-in-bio` — connecteur enrichissement (palier 1) | |
| 2 | + | |
| 3 | +_Fiche générée automatiquement par `scripts/gen_connector_docs.py` le 2026-08-18 03:30 — ne pas éditer à la main, régénérer._ | |
| 4 | + | |
| 5 | +**État : actif** · Famille : enrichissement (enrichment) · Backend : Firecrawl · Créateurs découverts : 0 · touchés : 0 | |
| 6 | + | |
| 7 | +## Description de la source | |
| 8 | + | |
| 9 | +Connecteur ENRICHISSEMENT générique « link-in-bio » (templatisé) — Linktree, Beacons, Bio.link, Milkshake, Campsite, Snipfeed. Mode d'accès : requêtes directes (pages publiques SSR), repli Firecrawl. Palier 1 du catalogue (§9) — le levier nº 1 : une page de liens révèle D'UN COUP tous les comptes d'un créateur. | |
| 10 | + | |
| 11 | +- **Notes (registre)** : une page → TOUS les comptes du créateur ; consomme les link_in_bio_url découvertes par instagram-profil/tiktok-profil | |
| 12 | +- **Signal d'identité** : link_in_bio (0.95) — le signal le plus fort (§12.1) (colonne `accounts.signal` / `confidence`) | |
| 13 | +- **Module** : `creaka/connectors/linkinbio.py` — classe `LinkInBioConnector` (`kind = "enrichment"`) | |
| 14 | + | |
| 15 | +## Accès | |
| 16 | + | |
| 17 | +- **Accès (registre)** : requêtes directes (pages publiques SSR : linktr.ee, beacons.ai, bio.link, msha.ke, campsite.bio, snipfeed.co), repli Firecrawl | |
| 18 | +- **Type d'accès (code)** : pages HTML publiques | |
| 19 | +- **Endpoint de base** : — | |
| 20 | +- **Pagination** : réponse unique (pas de pagination) | |
| 21 | +- **Backend anti-bot / rendu** : requests direct (session UA CreaKaBot, throttling poli) ; Firecrawl (HTML rendu, JavaScript exécuté) | |
| 22 | +- **Politesse** : 1.2 s entre requêtes, timeout 30 s, UA `CreaKaBot/1.0 (+https://www.crea-ka.com/bot)` | |
| 23 | +- **Authentification** : aucune — contenu public / API anonyme | |
| 24 | + | |
| 25 | +## Champs récupérés → schéma cible | |
| 26 | + | |
| 27 | +Aucune donnée attribuable à cette source en BD pour l'instant (clés manquantes ou source en attente) — schéma cible : `creators` + `accounts`. | |
| 28 | + | |
| 29 | +## Fréquence & budget | |
| 30 | + | |
| 31 | +- **Cadence déclarée (registre)** : mensuelle | |
| 32 | +- **Cadence observée** (médiane sync_log) : — | |
| 33 | +- **Dernier passage** : 2026-08-18T06:20:19Z — 74 créateurs, 288 comptes, +0 / ~74, 0 erreur(s), 114 s | |
| 34 | +- **Throttling** : 1.2 s entre requêtes (`request_delay`) | |
| 35 | + | |
| 36 | +## Volumétrie & complétude | |
| 37 | + | |
| 38 | +- **Créateurs découverts par la source** (`doc.source`) : 0 · **fiches touchées** (`source_ids`) : 0 | |
| 39 | +- **Runs journalisés (60 derniers)** : 2, dont 0 avec erreurs | |
| 40 | + | |
| 41 | +## Erreurs connues & dépannage | |
| 42 | + | |
| 43 | +Aucune erreur ni alerte dans les 60 derniers runs journalisés. | |
| 44 | + | |
| 45 | +Rejouer la source seule : `python3 run.py sync link-in-bio` · vérifier `sync_log` (`SELECT * FROM sync_log WHERE source='link-in-bio' ORDER BY ts DESC LIMIT 5;`). | |
| 46 | + | |
| 47 | +## Licence, attribution & conditions | |
| 48 | + | |
| 49 | +- **Cadre d'accès (registre)** : requêtes directes (pages publiques SSR : linktr.ee, beacons.ai, bio.link, msha.ke, campsite.bio, snipfeed.co), repli Firecrawl | |
| 50 | +- **Profils publics uniquement** : UA identifiable `CreaKaBot/1.0 (+https://www.crea-ka.com/bot; contact@spboucher.ai)`, throttling poli, respect des opt-out (`data/optout.json`) et du volet éthique (`creaka/ethics.py` — mineurs exclus via `is_minor`). | |
| 51 | +- Retrait sur demande : contact@spboucher.ai (opt-out honoré à la prochaine ingestion). | |
| 52 | + | |
| 53 | +## Historique | |
| 54 | + | |
| 55 | +- 2026-08-18 — plus ancien passage journalisé dans `sync_log` (fenêtre des 60 derniers). | |
| 56 | +- 2026-08-18 — vague d'enrichissement : standardisation de la documentation des connecteurs (fiche générée par `scripts/gen_connector_docs.py`). | |
added
docs/connecteurs/listes-medias.md
+72 −0
@@ -0,0 +1,72 @@ | ||
| 1 | +# `listes-medias` — connecteur decouverte (palier 3) | |
| 2 | + | |
| 3 | +_Fiche générée automatiquement par `scripts/gen_connector_docs.py` le 2026-08-18 03:30 — ne pas éditer à la main, régénérer._ | |
| 4 | + | |
| 5 | +**État : actif** · Famille : decouverte (discovery) · Backend : direct · Créateurs découverts : 310 · touchés : 310 | |
| 6 | + | |
| 7 | +## Description de la source | |
| 8 | + | |
| 9 | +Connecteur DÉCOUVERTE — listes & palmarès médias québécois compilés (dataset local data/seed/qc_influenceurs.json, croisement de listes publiques : billie, frank, metricool, Favikon QC…). Mode d'accès : dataset local (aucune requête réseau). Palier 3 du catalogue (§9). | |
| 10 | + | |
| 11 | +- **Notes (registre)** : 376 créateurs québécois, handles par plateforme + audiences approximatives | |
| 12 | +- **Signal d'identité** : listes_medias (0.85) (colonne `accounts.signal` / `confidence`) | |
| 13 | +- **Module** : `creaka/connectors/listes_medias.py` — classe `ListesMediasConnector` (`kind = "discovery"`) | |
| 14 | + | |
| 15 | +## Accès | |
| 16 | + | |
| 17 | +- **Accès (registre)** : dataset local (listes & palmarès médias publics compilés : billie, frank, metricool, Favikon QC, canadult…) | |
| 18 | +- **Type d'accès (code)** : dataset local compilé (data/seed) | |
| 19 | +- **Endpoint de base** : — | |
| 20 | +- **Pagination** : réponse unique (pas de pagination) | |
| 21 | +- **Backend anti-bot / rendu** : requests direct | |
| 22 | +- **Politesse** : 0.8 s entre requêtes, timeout 30 s, UA `CreaKaBot/1.0 (+https://www.crea-ka.com/bot)` | |
| 23 | +- **Authentification** : aucune — contenu public / API anonyme | |
| 24 | + | |
| 25 | +## Champs récupérés → schéma cible | |
| 26 | + | |
| 27 | +Source de **découverte** : produit des fiches `Creator` (tables `creators` + `accounts`). Complétude mesurée sur les 310 créateurs découverts par cette source (champ `doc.source`) ; exemple tiré d'une fiche réelle. | |
| 28 | + | |
| 29 | +| Champ du doc créateur | Contenu | Renseigné | Exemple réel | | |
| 30 | +|---|---|---|---| | |
| 31 | +| `display_name` | Nom public | 100 % | Enola Bédard | | |
| 32 | +| `bio` | Bio | 66 % | 📍Los Angeles 📩: [retiré] Tik Tok 17M Youtube 5M | | |
| 33 | +| `region` | Région | 49 % | — | | |
| 34 | +| `city` | Ville | 49 % | — | | |
| 35 | +| `niches` | Niches (liste) | 100 % | danse, lifestyle | | |
| 36 | +| `languages` | Langues (liste) | 100 % | en, fr | | |
| 37 | +| `creator_type` | Type de créateur | 100 % | createur-tiktok | | |
| 38 | +| `primary_platform` | Plateforme principale | 100 % | tiktok | | |
| 39 | +| `platforms` | Comptes sociaux (liste) | 100 % | tiktok:@enola.bedard, youtube:@enola.bedard, instagram:@enola.bedard | | |
| 40 | +| `avatar_url` | Avatar | 68 % | https://scontent-lga3-2.cdninstagram.com/v/t51.82787-19/515154150_18510310135012441_32506… | | |
| 41 | +| `total_reach` | Portée totale (abonnés cumulés) | 90 % | 24500000 | | |
| 42 | +| `link_in_bio_url` | Lien-en-bio | 23 % | — | | |
| 43 | + | |
| 44 | +## Fréquence & budget | |
| 45 | + | |
| 46 | +- **Cadence déclarée (registre)** : mensuelle (recompilation du dataset) | |
| 47 | +- **Cadence observée** (médiane sync_log) : ≈ 22 min | |
| 48 | +- **Dernier passage** : 2026-08-18T06:23:00Z — 310 créateurs, 448 comptes, +0 / ~310, 0 erreur(s), 0 s | |
| 49 | +- **Throttling** : 0.8 s entre requêtes (`request_delay`) | |
| 50 | + | |
| 51 | +## Volumétrie & complétude | |
| 52 | + | |
| 53 | +- **Créateurs découverts par la source** (`doc.source`) : 310 · **fiches touchées** (`source_ids`) : 310 | |
| 54 | +- **Complétude clé (découverts)** : niches 100 % · avatar 68 % · portée 90 % · région 49 % | |
| 55 | +- **Runs journalisés (60 derniers)** : 11, dont 0 avec erreurs | |
| 56 | + | |
| 57 | +## Erreurs connues & dépannage | |
| 58 | + | |
| 59 | +Aucune erreur ni alerte dans les 60 derniers runs journalisés. | |
| 60 | + | |
| 61 | +Rejouer la source seule : `python3 run.py sync listes-medias` · vérifier `sync_log` (`SELECT * FROM sync_log WHERE source='listes-medias' ORDER BY ts DESC LIMIT 5;`). | |
| 62 | + | |
| 63 | +## Licence, attribution & conditions | |
| 64 | + | |
| 65 | +- **Cadre d'accès (registre)** : dataset local (listes & palmarès médias publics compilés : billie, frank, metricool, Favikon QC, canadult…) | |
| 66 | +- **Données compilées** de listes et palmarès médias **publics** ; seuls des faits publics (nom, handle, audience approximative) sont conservés. | |
| 67 | +- Retrait sur demande : contact@spboucher.ai (opt-out honoré à la prochaine ingestion). | |
| 68 | + | |
| 69 | +## Historique | |
| 70 | + | |
| 71 | +- 2026-08-17 — plus ancien passage journalisé dans `sync_log` (fenêtre des 60 derniers). | |
| 72 | +- 2026-08-18 — vague d'enrichissement : standardisation de la documentation des connecteurs (fiche générée par `scripts/gen_connector_docs.py`). | |
added
docs/connecteurs/onlyqueb.md
+87 −0
@@ -0,0 +1,87 @@ | ||
| 1 | +# `onlyqueb` — connecteur decouverte (palier 3) | |
| 2 | + | |
| 3 | +_Fiche générée automatiquement par `scripts/gen_connector_docs.py` le 2026-08-18 03:30 — ne pas éditer à la main, régénérer._ | |
| 4 | + | |
| 5 | +**État : actif** · Famille : decouverte (discovery) · Backend : direct · Créateurs découverts : 253 · touchés : 253 | |
| 6 | + | |
| 7 | +## Description de la source | |
| 8 | + | |
| 9 | +Connecteur DÉCOUVERTE — OnlyQueb (onlyqueb.com), annuaire public de créatrices/créateurs québécois sur abonnement. Mode d'accès : requêtes directes (sitemap.xml + JSON-LD des pages profil, SSR). Palier 3 (§9). Les liens `sameAs` sont AUTO-DÉCLARÉS par la personne sur son profil → signal cross_link (0.90, §12.1). | |
| 10 | + | |
| 11 | +- **Notes (registre)** : annuaire public onlyqueb.com — créateurs québécois sur abonnement (18+) ; nom public, photo, liens OnlyFans/Instagram/TikTok/X | |
| 12 | +- **Signal d'identité** : cross_link (0.90) — liens sameAs auto-déclarés par la personne (colonne `accounts.signal` / `confidence`) | |
| 13 | +- **Module** : `creaka/connectors/onlyqueb.py` — classe `OnlyQuebConnector` (`kind = "discovery"`) | |
| 14 | + | |
| 15 | +## Accès | |
| 16 | + | |
| 17 | +- **Accès (registre)** : requêtes directes (sitemap.xml + JSON-LD ProfilePage des pages profil publiques SSR) | |
| 18 | +- **Type d'accès (code)** : sitemap XML + JSON-LD des pages profil (SSR) | |
| 19 | +- **Endpoint de base** : https://onlyqueb.com/sitemap.xml | |
| 20 | +- **URLs du module** : https://onlyqueb.com/sitemap.xml | |
| 21 | +- **Pagination** : réponse unique (pas de pagination) | |
| 22 | +- **Backend anti-bot / rendu** : requests direct (session UA CreaKaBot, throttling poli) | |
| 23 | +- **Politesse** : 1.0 s entre requêtes, timeout 30 s, UA `CreaKaBot/1.0 (+https://www.crea-ka.com/bot)` | |
| 24 | +- **Authentification** : aucune — contenu public / API anonyme | |
| 25 | + | |
| 26 | +## Champs récupérés → schéma cible | |
| 27 | + | |
| 28 | +Source de **découverte** : produit des fiches `Creator` (tables `creators` + `accounts`). Complétude mesurée sur les 253 créateurs découverts par cette source (champ `doc.source`) ; exemple tiré d'une fiche réelle. | |
| 29 | + | |
| 30 | +| Champ du doc créateur | Contenu | Renseigné | Exemple réel | | |
| 31 | +|---|---|---|---| | |
| 32 | +| `display_name` | Nom public | 100 % | Jade Lavoie | | |
| 33 | +| `bio` | Bio | 98 % | Deviens mon prochain acteur! 🔥 Je fais littéralement TOUT. Demande ce que tu veux et trou… | | |
| 34 | +| `region` | Région | 0 % | — | | |
| 35 | +| `city` | Ville | 0 % | — | | |
| 36 | +| `niches` | Niches (liste) | 100 % | autre | | |
| 37 | +| `languages` | Langues (liste) | 100 % | fr | | |
| 38 | +| `creator_type` | Type de créateur | 100 % | influenceur | | |
| 39 | +| `primary_platform` | Plateforme principale | 100 % | instagram | | |
| 40 | +| `platforms` | Comptes sociaux (liste) | 100 % | instagram:@jadelavoie, x:@missjadelavoie, onlyfans:@misslavoie | | |
| 41 | +| `avatar_url` | Avatar | 100 % | https://storage.googleapis.com/onlyqueb/MQZBiXsbMQa31zxj9P_mabNxNITFBxa6 | | |
| 42 | +| `total_reach` | Portée totale (abonnés cumulés) | 16 % | 1571113 | | |
| 43 | +| `link_in_bio_url` | Lien-en-bio | 2 % | — | | |
| 44 | + | |
| 45 | +Cible d'**enrichissement** : colonnes de la table `accounts` pour la/les plateforme(s) `onlyfans` (248 comptes en BD, confiance moyenne 0.90). | |
| 46 | + | |
| 47 | +| Colonne `accounts` | Contenu | Renseignée | Exemple réel | | |
| 48 | +|---|---|---|---| | |
| 49 | +| `handle` | Handle | 100 % | — | | |
| 50 | +| `url` | URL du profil | 100 % | — | | |
| 51 | +| `followers` | Abonnés | 0 % | — | | |
| 52 | +| `verified` | Badge vérifié | 0 % | — | | |
| 53 | +| `confidence` | Confiance d'identité | 100 % | — | | |
| 54 | +| `signal` | Signal d'identité | 100 % | — | | |
| 55 | +| `metrics` | Métriques détaillées (JSON) | 0 % | — | | |
| 56 | +| `last_checked` | Dernière vérification | 100 % | — | | |
| 57 | + | |
| 58 | +## Fréquence & budget | |
| 59 | + | |
| 60 | +- **Cadence déclarée (registre)** : hebdomadaire | |
| 61 | +- **Cadence observée** (médiane sync_log) : ≈ 24 min | |
| 62 | +- **Dernier passage** : 2026-08-18T06:27:32Z — 253 créateurs, 611 comptes, +0 / ~253, 0 erreur(s), 272 s | |
| 63 | +- **Throttling** : 1.0 s entre requêtes (`request_delay`) | |
| 64 | + | |
| 65 | +## Volumétrie & complétude | |
| 66 | + | |
| 67 | +- **Créateurs découverts par la source** (`doc.source`) : 253 · **fiches touchées** (`source_ids`) : 253 | |
| 68 | +- **Complétude clé (découverts)** : niches 100 % · avatar 100 % · portée 16 % · région 0 % | |
| 69 | +- **Comptes `onlyfans` en BD** : 248 — abonnés 0 % · métriques 0 % · dernière vérification 2026-08-18T06:27:32Z | |
| 70 | +- **Runs journalisés (60 derniers)** : 7, dont 0 avec erreurs | |
| 71 | + | |
| 72 | +## Erreurs connues & dépannage | |
| 73 | + | |
| 74 | +Aucune erreur ni alerte dans les 60 derniers runs journalisés. | |
| 75 | + | |
| 76 | +Rejouer la source seule : `python3 run.py sync onlyqueb` · vérifier `sync_log` (`SELECT * FROM sync_log WHERE source='onlyqueb' ORDER BY ts DESC LIMIT 5;`). | |
| 77 | + | |
| 78 | +## Licence, attribution & conditions | |
| 79 | + | |
| 80 | +- **Cadre d'accès (registre)** : requêtes directes (sitemap.xml + JSON-LD ProfilePage des pages profil publiques SSR) | |
| 81 | +- **Profils publics uniquement** : UA identifiable `CreaKaBot/1.0 (+https://www.crea-ka.com/bot; contact@spboucher.ai)`, throttling poli, respect des opt-out (`data/optout.json`) et du volet éthique (`creaka/ethics.py` — mineurs exclus via `is_minor`). | |
| 82 | +- Retrait sur demande : contact@spboucher.ai (opt-out honoré à la prochaine ingestion). | |
| 83 | + | |
| 84 | +## Historique | |
| 85 | + | |
| 86 | +- 2026-08-17 — plus ancien passage journalisé dans `sync_log` (fenêtre des 60 derniers). | |
| 87 | +- 2026-08-18 — vague d'enrichissement : standardisation de la documentation des connecteurs (fiche générée par `scripts/gen_connector_docs.py`). | |
added
docs/connecteurs/podcastindex.md
+69 −0
@@ -0,0 +1,69 @@ | ||
| 1 | +# `podcastindex` — connecteur enrichissement (palier 2) | |
| 2 | + | |
| 3 | +_Fiche générée automatiquement par `scripts/gen_connector_docs.py` le 2026-08-18 03:30 — ne pas éditer à la main, régénérer._ | |
| 4 | + | |
| 5 | +**État : prêt — clés requises** · Famille : enrichissement (enrichment) · Backend : direct · Créateurs découverts : 0 · touchés : 0 | |
| 6 | + | |
| 7 | +## Description de la source | |
| 8 | + | |
| 9 | +Connecteur ENRICHISSEMENT « podcastindex » — API Podcast Index (OFFICIELLE, gratuite ; clé + secret requis, inscription gratuite : https://api.podcastindex.org). Palier 2 (§9, §10). Sans clés : passage sauté PROPREMENT (SkipSource, comme twitch). | |
| 10 | + | |
| 11 | +- **Notes (registre)** : episodeCount, lastUpdateTime, itunesId, image, catégories des balados par feed_url. En attendant les clés, balados-rss couvre les mêmes champs sans clé. | |
| 12 | +- **Signal d'identité** : api_officielle (0.95) (colonne `accounts.signal` / `confidence`) | |
| 13 | +- **Module** : `creaka/connectors/podcastindex.py` — classe `PodcastIndexConnector` (`kind = "enrichment"`) | |
| 14 | + | |
| 15 | +## Accès | |
| 16 | + | |
| 17 | +- **Accès (registre)** : API Podcast Index (officielle, GRATUITE — inscription requise : https://api.podcastindex.org ; PODCASTINDEX_API_KEY/SECRET dans .env) — sauté proprement sans clés | |
| 18 | +- **Type d'accès (code)** : API JSON | |
| 19 | +- **Endpoint de base** : https://api.podcastindex.org/api/1.0/podcasts/byfeedurl | |
| 20 | +- **URLs du module** : https://api.podcastindex.org · https://api.podcastindex.org/api/1.0/podcasts/byfeedurl | |
| 21 | +- **Pagination** : réponse unique (pas de pagination) | |
| 22 | +- **Backend anti-bot / rendu** : requests direct (session UA CreaKaBot, throttling poli) | |
| 23 | +- **Politesse** : 0.4 s entre requêtes, timeout 30 s, UA `CreaKaBot/1.0 (+https://www.crea-ka.com/bot)` | |
| 24 | +- **Authentification** : clé(s) API requise(s) — voir statut/registre | |
| 25 | + | |
| 26 | +## Champs récupérés → schéma cible | |
| 27 | + | |
| 28 | +Cible d'**enrichissement** : colonnes de la table `accounts` pour la/les plateforme(s) `podcast` (434 comptes en BD, confiance moyenne 0.98). | |
| 29 | + | |
| 30 | +| Colonne `accounts` | Contenu | Renseignée | Exemple réel | | |
| 31 | +|---|---|---|---| | |
| 32 | +| `handle` | Handle | 100 % | — | | |
| 33 | +| `url` | URL du profil | 100 % | — | | |
| 34 | +| `followers` | Abonnés | 0 % | — | | |
| 35 | +| `verified` | Badge vérifié | 0 % | — | | |
| 36 | +| `confidence` | Confiance d'identité | 100 % | — | | |
| 37 | +| `signal` | Signal d'identité | 100 % | — | | |
| 38 | +| `metrics` | Métriques détaillées (JSON) | 100 % | — | | |
| 39 | +| `last_checked` | Dernière vérification | 100 % | — | | |
| 40 | + | |
| 41 | +## Fréquence & budget | |
| 42 | + | |
| 43 | +- **Cadence déclarée (registre)** : quotidienne (watch), re-visite 7 j par balado | |
| 44 | +- **Cadence observée** (médiane sync_log) : — | |
| 45 | +- **Throttling** : 0.4 s entre requêtes (`request_delay`) | |
| 46 | + | |
| 47 | +## Volumétrie & complétude | |
| 48 | + | |
| 49 | +- **Créateurs découverts par la source** (`doc.source`) : 0 · **fiches touchées** (`source_ids`) : 0 | |
| 50 | +- **Comptes `podcast` en BD** : 434 — abonnés 0 % · métriques 100 % · dernière vérification 2026-08-18T06:23:00Z | |
| 51 | +- **Runs journalisés (60 derniers)** : 0, dont 0 avec erreurs | |
| 52 | + | |
| 53 | +## Erreurs connues & dépannage | |
| 54 | + | |
| 55 | +Aucune erreur ni alerte dans les 60 derniers runs journalisés. | |
| 56 | + | |
| 57 | +**Statut du registre** : prêt — clés requises | |
| 58 | + | |
| 59 | +Rejouer la source seule : `python3 run.py sync podcastindex` · vérifier `sync_log` (`SELECT * FROM sync_log WHERE source='podcastindex' ORDER BY ts DESC LIMIT 5;`). | |
| 60 | + | |
| 61 | +## Licence, attribution & conditions | |
| 62 | + | |
| 63 | +- **Cadre d'accès (registre)** : API Podcast Index (officielle, GRATUITE — inscription requise : https://api.podcastindex.org ; PODCASTINDEX_API_KEY/SECRET dans .env) — sauté proprement sans clés | |
| 64 | +- **Profils publics uniquement** : UA identifiable `CreaKaBot/1.0 (+https://www.crea-ka.com/bot; contact@spboucher.ai)`, throttling poli, respect des opt-out (`data/optout.json`) et du volet éthique (`creaka/ethics.py` — mineurs exclus via `is_minor`). | |
| 65 | +- Retrait sur demande : contact@spboucher.ai (opt-out honoré à la prochaine ingestion). | |
| 66 | + | |
| 67 | +## Historique | |
| 68 | + | |
| 69 | +- 2026-08-18 — vague d'enrichissement : standardisation de la documentation des connecteurs (fiche générée par `scripts/gen_connector_docs.py`). | |
added
docs/connecteurs/tiktok-profil.md
+69 −0
@@ -0,0 +1,69 @@ | ||
| 1 | +# `tiktok-profil` — connecteur enrichissement (palier 4) | |
| 2 | + | |
| 3 | +_Fiche générée automatiquement par `scripts/gen_connector_docs.py` le 2026-08-18 03:30 — ne pas éditer à la main, régénérer._ | |
| 4 | + | |
| 5 | +**État : actif** · Famille : enrichissement (enrichment) · Backend : Scrapfly · Créateurs découverts : 0 · touchés : 0 | |
| 6 | + | |
| 7 | +## Description de la source | |
| 8 | + | |
| 9 | +Connecteur ENRICHISSEMENT TikTok — profil public par créateur : abonnés, badge vérifié, bio, lien de bio (découverte croisée). Mode d'accès : Scrapfly (plateforme hostile au scraping, API officielle limitée aux comptes connectés) — palier 4 (§9), §15. | |
| 10 | + | |
| 11 | +- **Notes (registre)** : abonnés, badge, bio, lien de bio | |
| 12 | +- **Signal d'identité** : cross_link (0.90) pour le lien de bio (colonne `accounts.signal` / `confidence`) | |
| 13 | +- **Module** : `creaka/connectors/tiktok.py` — classe `TikTokConnector` (`kind = "enrichment"`) | |
| 14 | + | |
| 15 | +## Accès | |
| 16 | + | |
| 17 | +- **Accès (registre)** : Scrapfly (JSON d'hydratation de la page publique @handle) | |
| 18 | +- **Type d'accès (code)** : pages HTML publiques | |
| 19 | +- **Endpoint de base** : https://www.tiktok.com/@{h} | |
| 20 | +- **URLs du module** : https://www.tiktok.com/@{h} | |
| 21 | +- **Pagination** : réponse unique (pas de pagination) | |
| 22 | +- **Backend anti-bot / rendu** : Scrapfly (asp + render_js — contournement anti-bot) | |
| 23 | +- **Politesse** : 1.5 s entre requêtes, timeout 30 s, UA `CreaKaBot/1.0 (+https://www.crea-ka.com/bot)` | |
| 24 | +- **Authentification** : aucune — contenu public / API anonyme | |
| 25 | + | |
| 26 | +## Champs récupérés → schéma cible | |
| 27 | + | |
| 28 | +Cible d'**enrichissement** : colonnes de la table `accounts` pour la/les plateforme(s) `tiktok` (197 comptes en BD, confiance moyenne 0.90). | |
| 29 | + | |
| 30 | +| Colonne `accounts` | Contenu | Renseignée | Exemple réel | | |
| 31 | +|---|---|---|---| | |
| 32 | +| `handle` | Handle | 100 % | enola.bedard | | |
| 33 | +| `url` | URL du profil | 100 % | https://www.tiktok.com/@enola.bedard | | |
| 34 | +| `followers` | Abonnés | 53 % | 17000000 | | |
| 35 | +| `verified` | Badge vérifié | 35 % | 1 | | |
| 36 | +| `confidence` | Confiance d'identité | 100 % | 0.85 | | |
| 37 | +| `signal` | Signal d'identité | 100 % | base_publique | | |
| 38 | +| `metrics` | Métriques détaillées (JSON) | 35 % | {"following": 813, "likes": 438100000, "videos": 2847} | | |
| 39 | +| `last_checked` | Dernière vérification | 100 % | 2026-08-18T06:27:51Z | | |
| 40 | + | |
| 41 | +## Fréquence & budget | |
| 42 | + | |
| 43 | +- **Cadence déclarée (registre)** : hebdomadaire (gros créateurs) / mensuelle | |
| 44 | +- **Cadence observée** (médiane sync_log) : — | |
| 45 | +- **Dernier passage** : 2026-08-18T00:31:47Z — 68 créateurs, 196 comptes, +0 / ~68, 0 erreur(s), 0 s | |
| 46 | +- **Throttling** : 1.5 s entre requêtes (`request_delay`) | |
| 47 | + | |
| 48 | +## Volumétrie & complétude | |
| 49 | + | |
| 50 | +- **Créateurs découverts par la source** (`doc.source`) : 0 · **fiches touchées** (`source_ids`) : 0 | |
| 51 | +- **Comptes `tiktok` en BD** : 197 — abonnés 53 % · métriques 35 % · dernière vérification 2026-08-18T06:27:51Z | |
| 52 | +- **Runs journalisés (60 derniers)** : 1, dont 0 avec erreurs | |
| 53 | + | |
| 54 | +## Erreurs connues & dépannage | |
| 55 | + | |
| 56 | +Aucune erreur ni alerte dans les 60 derniers runs journalisés. | |
| 57 | + | |
| 58 | +Rejouer la source seule : `python3 run.py sync tiktok-profil` · vérifier `sync_log` (`SELECT * FROM sync_log WHERE source='tiktok-profil' ORDER BY ts DESC LIMIT 5;`). | |
| 59 | + | |
| 60 | +## Licence, attribution & conditions | |
| 61 | + | |
| 62 | +- **Cadre d'accès (registre)** : Scrapfly (JSON d'hydratation de la page publique @handle) | |
| 63 | +- **Profils publics uniquement** : UA identifiable `CreaKaBot/1.0 (+https://www.crea-ka.com/bot; contact@spboucher.ai)`, throttling poli, respect des opt-out (`data/optout.json`) et du volet éthique (`creaka/ethics.py` — mineurs exclus via `is_minor`). | |
| 64 | +- Retrait sur demande : contact@spboucher.ai (opt-out honoré à la prochaine ingestion). | |
| 65 | + | |
| 66 | +## Historique | |
| 67 | + | |
| 68 | +- 2026-08-18 — plus ancien passage journalisé dans `sync_log` (fenêtre des 60 derniers). | |
| 69 | +- 2026-08-18 — vague d'enrichissement : standardisation de la documentation des connecteurs (fiche générée par `scripts/gen_connector_docs.py`). | |
added
docs/connecteurs/twitch.md
+71 −0
@@ -0,0 +1,71 @@ | ||
| 1 | +# `twitch` — connecteur enrichissement (palier 2) | |
| 2 | + | |
| 3 | +_Fiche générée automatiquement par `scripts/gen_connector_docs.py` le 2026-08-18 03:30 — ne pas éditer à la main, régénérer._ | |
| 4 | + | |
| 5 | +**État : actif** · Famille : enrichissement (enrichment) · Backend : direct · Créateurs découverts : 0 · touchés : 0 | |
| 6 | + | |
| 7 | +## Description de la source | |
| 8 | + | |
| 9 | +Connecteur ENRICHISSEMENT Twitch — existence + profil par créateur via l'API OFFICIELLE Helix (client_credentials). Palier 2 (§9, §10). Sans TWITCH_CLIENT_ID/TWITCH_CLIENT_SECRET : passage sauté proprement. | |
| 10 | + | |
| 11 | +- **Notes (registre)** : existence, type de diffuseur, description | |
| 12 | +- **Signal d'identité** : api_officielle (0.95) (colonne `accounts.signal` / `confidence`) | |
| 13 | +- **Module** : `creaka/connectors/twitch.py` — classe `TwitchConnector` (`kind = "enrichment"`) | |
| 14 | + | |
| 15 | +## Accès | |
| 16 | + | |
| 17 | +- **Accès (registre)** : API officielle Helix (client_credentials) — sauté sans clés | |
| 18 | +- **Type d'accès (code)** : API JSON | |
| 19 | +- **Endpoint de base** : https://id.twitch.tv/oauth2/token | |
| 20 | +- **URLs du module** : https://id.twitch.tv/oauth2/token · https://api.twitch.tv/helix/users | |
| 21 | +- **Pagination** : réponse unique (pas de pagination) | |
| 22 | +- **Backend anti-bot / rendu** : requests direct (session UA CreaKaBot, throttling poli) | |
| 23 | +- **Politesse** : 0.5 s entre requêtes, timeout 30 s, UA `CreaKaBot/1.0 (+https://www.crea-ka.com/bot)` | |
| 24 | +- **Authentification** : clé(s) API requise(s) — voir statut/registre | |
| 25 | + | |
| 26 | +## Champs récupérés → schéma cible | |
| 27 | + | |
| 28 | +Cible d'**enrichissement** : colonnes de la table `accounts` pour la/les plateforme(s) `twitch` (26 comptes en BD, confiance moyenne 0.89). | |
| 29 | + | |
| 30 | +| Colonne `accounts` | Contenu | Renseignée | Exemple réel | | |
| 31 | +|---|---|---|---| | |
| 32 | +| `handle` | Handle | 100 % | heyraion | | |
| 33 | +| `url` | URL du profil | 100 % | https://www.twitch.tv/heyraion | | |
| 34 | +| `followers` | Abonnés | 15 % | 37900 | | |
| 35 | +| `verified` | Badge vérifié | 0 % | — | | |
| 36 | +| `confidence` | Confiance d'identité | 100 % | 0.85 | | |
| 37 | +| `signal` | Signal d'identité | 100 % | listes_medias | | |
| 38 | +| `metrics` | Métriques détaillées (JSON) | 0 % | — | | |
| 39 | +| `last_checked` | Dernière vérification | 100 % | 2026-08-18T06:23:00Z | | |
| 40 | + | |
| 41 | +## Fréquence & budget | |
| 42 | + | |
| 43 | +- **Cadence déclarée (registre)** : hebdomadaire | |
| 44 | +- **Cadence observée** (médiane sync_log) : — | |
| 45 | +- **Dernier passage** : 2026-08-18T01:06:19Z — 0 créateurs, 0 comptes, +0 / ~0, 0 erreur(s), 0 s | |
| 46 | +- **Throttling** : 0.5 s entre requêtes (`request_delay`) | |
| 47 | + | |
| 48 | +## Volumétrie & complétude | |
| 49 | + | |
| 50 | +- **Créateurs découverts par la source** (`doc.source`) : 0 · **fiches touchées** (`source_ids`) : 0 | |
| 51 | +- **Comptes `twitch` en BD** : 26 — abonnés 15 % · métriques 0 % · dernière vérification 2026-08-18T06:27:51Z | |
| 52 | +- **Runs journalisés (60 derniers)** : 1, dont 0 avec erreurs | |
| 53 | + | |
| 54 | +## Erreurs connues & dépannage | |
| 55 | + | |
| 56 | +| Passage | Erreurs | Alerte (sync_log) | | |
| 57 | +|---|---|---| | |
| 58 | +| 2026-08-18T01:06:19Z | 0 | 0 créateur retourné — source possiblement bloquée | | |
| 59 | + | |
| 60 | +Rejouer la source seule : `python3 run.py sync twitch` · vérifier `sync_log` (`SELECT * FROM sync_log WHERE source='twitch' ORDER BY ts DESC LIMIT 5;`). | |
| 61 | + | |
| 62 | +## Licence, attribution & conditions | |
| 63 | + | |
| 64 | +- **Cadre d'accès (registre)** : API officielle Helix (client_credentials) — sauté sans clés | |
| 65 | +- **Profils publics uniquement** : UA identifiable `CreaKaBot/1.0 (+https://www.crea-ka.com/bot; contact@spboucher.ai)`, throttling poli, respect des opt-out (`data/optout.json`) et du volet éthique (`creaka/ethics.py` — mineurs exclus via `is_minor`). | |
| 66 | +- Retrait sur demande : contact@spboucher.ai (opt-out honoré à la prochaine ingestion). | |
| 67 | + | |
| 68 | +## Historique | |
| 69 | + | |
| 70 | +- 2026-08-18 — plus ancien passage journalisé dans `sync_log` (fenêtre des 60 derniers). | |
| 71 | +- 2026-08-18 — vague d'enrichissement : standardisation de la documentation des connecteurs (fiche générée par `scripts/gen_connector_docs.py`). | |
added
docs/connecteurs/wikidata-qc.md
+75 −0
@@ -0,0 +1,75 @@ | ||
| 1 | +# `wikidata-qc` — connecteur decouverte (palier 3) | |
| 2 | + | |
| 3 | +_Fiche générée automatiquement par `scripts/gen_connector_docs.py` le 2026-08-18 03:30 — ne pas éditer à la main, régénérer._ | |
| 4 | + | |
| 5 | +**État : actif** · Famille : decouverte (discovery) · Backend : direct · Créateurs découverts : 73 · touchés : 73 | |
| 6 | + | |
| 7 | +## Description de la source | |
| 8 | + | |
| 9 | +Connecteur DÉCOUVERTE — créateurs québécois recensés dans Wikidata (personnes nées ou résidant au Québec, occupation créateur, avec handles sociaux curés P2003/P7085/P2397/P5797/P2002). Mode d'accès : API SPARQL publique de Wikidata (gratuite, conforme). Palier 3 (§9). | |
| 10 | + | |
| 11 | +- **Notes (registre)** : humoristes, youtubeurs, vidéastes web, streamers, podcasteurs notables ; is_minor levé via l'année de naissance publique | |
| 12 | +- **Signal d'identité** : base_publique (0.85) (colonne `accounts.signal` / `confidence`) | |
| 13 | +- **Module** : `creaka/connectors/wikidata_qc.py` — classe `WikidataQcConnector` (`kind = "discovery"`) | |
| 14 | + | |
| 15 | +## Accès | |
| 16 | + | |
| 17 | +- **Accès (registre)** : API SPARQL publique Wikidata (nés/résidant au Québec, occupation créateur, handles sociaux curés) | |
| 18 | +- **Type d'accès (code)** : API SPARQL publique (Wikidata) | |
| 19 | +- **Endpoint de base** : https://query.wikidata.org/sparql | |
| 20 | +- **URLs du module** : https://query.wikidata.org/sparql · https://www.youtube.com/channel/{handle} | |
| 21 | +- **Pagination** : réponse unique (pas de pagination) | |
| 22 | +- **Backend anti-bot / rendu** : requests direct (session UA CreaKaBot, throttling poli) | |
| 23 | +- **Politesse** : 2.0 s entre requêtes, timeout 90 s, UA `CreaKaBot/1.0 (+https://www.crea-ka.com/bot)` | |
| 24 | +- **Authentification** : aucune — contenu public / API anonyme | |
| 25 | + | |
| 26 | +## Champs récupérés → schéma cible | |
| 27 | + | |
| 28 | +Source de **découverte** : produit des fiches `Creator` (tables `creators` + `accounts`). Complétude mesurée sur les 73 créateurs découverts par cette source (champ `doc.source`) ; exemple tiré d'une fiche réelle. | |
| 29 | + | |
| 30 | +| Champ du doc créateur | Contenu | Renseigné | Exemple réel | | |
| 31 | +|---|---|---|---| | |
| 32 | +| `display_name` | Nom public | 100 % | Nigel Braun | | |
| 33 | +| `bio` | Bio | 60 % | Capturing the natural beauty of chemistry Find me on YouTube: @NileRed/@NileBlue | | |
| 34 | +| `region` | Région | 0 % | — | | |
| 35 | +| `city` | Ville | 0 % | — | | |
| 36 | +| `niches` | Niches (liste) | 100 % | lifestyle | | |
| 37 | +| `languages` | Langues (liste) | 100 % | fr | | |
| 38 | +| `creator_type` | Type de créateur | 100 % | influenceur | | |
| 39 | +| `primary_platform` | Plateforme principale | 100 % | youtube | | |
| 40 | +| `platforms` | Comptes sociaux (liste) | 99 % | youtube:@nilered, instagram:@nile.red, x:@nilered2 | | |
| 41 | +| `avatar_url` | Avatar | 63 % | https://scontent-yyz1-1.cdninstagram.com/v/t51.2885-19/441008830_1124225362150102_4410959… | | |
| 42 | +| `total_reach` | Portée totale (abonnés cumulés) | 63 % | 12245225 | | |
| 43 | +| `link_in_bio_url` | Lien-en-bio | 19 % | — | | |
| 44 | + | |
| 45 | +## Fréquence & budget | |
| 46 | + | |
| 47 | +- **Cadence déclarée (registre)** : hebdomadaire | |
| 48 | +- **Cadence observée** (médiane sync_log) : ≈ 30 min | |
| 49 | +- **Dernier passage** : 2026-08-18T06:27:51Z — 83 créateurs, 172 comptes, +0 / ~83, 0 erreur(s), 18 s | |
| 50 | +- **Throttling** : 2.0 s entre requêtes (`request_delay`) | |
| 51 | + | |
| 52 | +## Volumétrie & complétude | |
| 53 | + | |
| 54 | +- **Créateurs découverts par la source** (`doc.source`) : 73 · **fiches touchées** (`source_ids`) : 73 | |
| 55 | +- **Complétude clé (découverts)** : niches 100 % · avatar 63 % · portée 63 % · région 0 % | |
| 56 | +- **Runs journalisés (60 derniers)** : 9, dont 1 avec erreurs | |
| 57 | + | |
| 58 | +## Erreurs connues & dépannage | |
| 59 | + | |
| 60 | +| Passage | Erreurs | Alerte (sync_log) | | |
| 61 | +|---|---|---| | |
| 62 | +| 2026-08-18T05:55:35Z | 1 | — | | |
| 63 | + | |
| 64 | +Rejouer la source seule : `python3 run.py sync wikidata-qc` · vérifier `sync_log` (`SELECT * FROM sync_log WHERE source='wikidata-qc' ORDER BY ts DESC LIMIT 5;`). | |
| 65 | + | |
| 66 | +## Licence, attribution & conditions | |
| 67 | + | |
| 68 | +- **Cadre d'accès (registre)** : API SPARQL publique Wikidata (nés/résidant au Québec, occupation créateur, handles sociaux curés) | |
| 69 | +- **Licence** : données Wikidata sous CC0 — réutilisation libre, mention « Source : Wikidata » affichée par courtoisie. | |
| 70 | +- Retrait sur demande : contact@spboucher.ai (opt-out honoré à la prochaine ingestion). | |
| 71 | + | |
| 72 | +## Historique | |
| 73 | + | |
| 74 | +- 2026-08-17 — plus ancien passage journalisé dans `sync_log` (fenêtre des 60 derniers). | |
| 75 | +- 2026-08-18 — vague d'enrichissement : standardisation de la documentation des connecteurs (fiche générée par `scripts/gen_connector_docs.py`). | |
added
docs/connecteurs/x-profil.md
+74 −0
@@ -0,0 +1,74 @@ | ||
| 1 | +# `x-profil` — connecteur enrichissement (palier 3) | |
| 2 | + | |
| 3 | +_Fiche générée automatiquement par `scripts/gen_connector_docs.py` le 2026-08-18 03:30 — ne pas éditer à la main, régénérer._ | |
| 4 | + | |
| 5 | +**État : actif** · Famille : enrichissement (enrichment) · Backend : direct · Créateurs découverts : 0 · touchés : 0 | |
| 6 | + | |
| 7 | +## Description de la source | |
| 8 | + | |
| 9 | +Connecteur ENRICHISSEMENT « x-profil » — abonnés X (Twitter) SANS clé via le service public de syndication (widgets d'intégration officiels) syndication.twitter.com/srv/timeline-profile/screen-name/{handle}, repli page publique x.com/{handle}. Palier 3 (§9). Facebook sondé aussi : page publique bloquée sans clé (HTTP 400) → aucune voie fiable, on passe (documenté dans data/sources.json). | |
| 10 | + | |
| 11 | +- **Notes (registre)** : abonnés + abonnements + badge + avatar X. Ancien widget followbutton MORT (vérifié 2026-08-18 : réponse vide) ; page x.com : mur de connexion après ~15-20 req/IP (constaté 2026-08-18) → repli seulement. Facebook : page publique bloquée sans clé (HTTP 400, vérifié 2026-08-18) et Scrapfly non justifié → aucune voie fiable, on passe. | |
| 12 | +- **Signal d'identité** : aucun nouveau rattachement — métriques du compte déjà rattaché seulement (colonne `accounts.signal` / `confidence`) | |
| 13 | +- **Module** : `creaka/connectors/x_profil.py` — classe `XProfilConnector` (`kind = "enrichment"`) | |
| 14 | + | |
| 15 | +## Accès | |
| 16 | + | |
| 17 | +- **Accès (registre)** : requêtes directes (service public de syndication des widgets officiels : syndication.twitter.com/srv/timeline-profile — objet user complet dans __NEXT_DATA__ ; repli très limité page x.com) ; cap 150/passage, re-visite 7 j, délai 2 s | |
| 18 | +- **Type d'accès (code)** : pages HTML publiques | |
| 19 | +- **Endpoint de base** : https://syndication.twitter.com/srv/timeline-profile/ | |
| 20 | +- **URLs du module** : https://syndication.twitter.com/srv/timeline-profile/ · https://x.com/{h} | |
| 21 | +- **Pagination** : réponse unique (pas de pagination) | |
| 22 | +- **Backend anti-bot / rendu** : requests direct | |
| 23 | +- **Politesse** : 2.0 s entre requêtes, timeout 15 s, UA `CreaKaBot/1.0 (+https://www.crea-ka.com/bot)` | |
| 24 | +- **Authentification** : aucune — contenu public / API anonyme | |
| 25 | + | |
| 26 | +## Champs récupérés → schéma cible | |
| 27 | + | |
| 28 | +Cible d'**enrichissement** : colonnes de la table `accounts` pour la/les plateforme(s) `x` (186 comptes en BD, confiance moyenne 0.89). | |
| 29 | + | |
| 30 | +| Colonne `accounts` | Contenu | Renseignée | Exemple réel | | |
| 31 | +|---|---|---|---| | |
| 32 | +| `handle` | Handle | 100 % | xqc | | |
| 33 | +| `url` | URL du profil | 100 % | https://x.com/xqc | | |
| 34 | +| `followers` | Abonnés | 10 % | 1598794 | | |
| 35 | +| `verified` | Badge vérifié | 0 % | — | | |
| 36 | +| `confidence` | Confiance d'identité | 100 % | 0.9 | | |
| 37 | +| `signal` | Signal d'identité | 100 % | cross_link | | |
| 38 | +| `metrics` | Métriques détaillées (JSON) | 9 % | {"following": 526, "x_checked": "2026-08-18T06:14:55Z"} | | |
| 39 | +| `last_checked` | Dernière vérification | 100 % | 2026-08-18T06:27:51Z | | |
| 40 | + | |
| 41 | +## Fréquence & budget | |
| 42 | + | |
| 43 | +- **Cadence déclarée (registre)** : quotidienne (watch), rotation hebdomadaire | |
| 44 | +- **Cadence observée** (médiane sync_log) : ≈ 6 min | |
| 45 | +- **Dernier passage** : 2026-08-18T06:15:49Z — 9 créateurs, 35 comptes, +0 / ~9, 1 erreur(s), 68 s | |
| 46 | +- **Throttling** : 2.0 s entre requêtes (`request_delay`) | |
| 47 | + | |
| 48 | +## Volumétrie & complétude | |
| 49 | + | |
| 50 | +- **Créateurs découverts par la source** (`doc.source`) : 0 · **fiches touchées** (`source_ids`) : 0 | |
| 51 | +- **Comptes `x` en BD** : 186 — abonnés 10 % · métriques 9 % · dernière vérification 2026-08-18T06:27:51Z | |
| 52 | +- **Runs journalisés (60 derniers)** : 3, dont 3 avec erreurs | |
| 53 | + | |
| 54 | +## Erreurs connues & dépannage | |
| 55 | + | |
| 56 | +| Passage | Erreurs | Alerte (sync_log) | | |
| 57 | +|---|---|---| | |
| 58 | +| 2026-08-18T06:15:49Z | 1 | — | | |
| 59 | +| 2026-08-18T06:12:35Z | 171 | — | | |
| 60 | +| 2026-08-18T06:02:56Z | 1 | — | | |
| 61 | + | |
| 62 | +Rejouer la source seule : `python3 run.py sync x-profil` · vérifier `sync_log` (`SELECT * FROM sync_log WHERE source='x-profil' ORDER BY ts DESC LIMIT 5;`). | |
| 63 | + | |
| 64 | +## Licence, attribution & conditions | |
| 65 | + | |
| 66 | +- **Cadre d'accès (registre)** : requêtes directes (service public de syndication des widgets officiels : syndication.twitter.com/srv/timeline-profile — objet user complet dans __NEXT_DATA__ ; repli très limité page x.com) ; cap 150/passage, re-visite 7 j, délai 2 s | |
| 67 | +- **Profils publics uniquement** : UA identifiable `CreaKaBot/1.0 (+https://www.crea-ka.com/bot; contact@spboucher.ai)`, throttling poli, respect des opt-out (`data/optout.json`) et du volet éthique (`creaka/ethics.py` — mineurs exclus via `is_minor`). | |
| 68 | +- Retrait sur demande : contact@spboucher.ai (opt-out honoré à la prochaine ingestion). | |
| 69 | + | |
| 70 | +## Historique | |
| 71 | + | |
| 72 | +- 2026-08-18 — date mentionnée au registre (voir notes/statut). | |
| 73 | +- 2026-08-18 — plus ancien passage journalisé dans `sync_log` (fenêtre des 60 derniers). | |
| 74 | +- 2026-08-18 — vague d'enrichissement : standardisation de la documentation des connecteurs (fiche générée par `scripts/gen_connector_docs.py`). | |
added
docs/connecteurs/youtube-recherche.md
+87 −0
@@ -0,0 +1,87 @@ | ||
| 1 | +# `youtube-recherche` — connecteur decouverte (palier 3) | |
| 2 | + | |
| 3 | +_Fiche générée automatiquement par `scripts/gen_connector_docs.py` le 2026-08-18 03:30 — ne pas éditer à la main, régénérer._ | |
| 4 | + | |
| 5 | +**État : actif** · Famille : decouverte (discovery) · Backend : direct · Créateurs découverts : 4595 · touchés : 4595 | |
| 6 | + | |
| 7 | +## Description de la source | |
| 8 | + | |
| 9 | +Connecteur DÉCOUVERTE — chaînes YouTube québécoises via la recherche publique (filtre « chaînes »), requêtes thématiques × marqueurs QC. Mode d'accès : requêtes directes (HTML public, ytInitialData). Palier 3 (§9). Filtre conservateur : marqueur québécois OBLIGATOIRE dans le titre ou la description de la chaîne. | |
| 10 | + | |
| 11 | +- **Notes (registre)** : filtre conservateur : marqueur québécois requis dans titre/description de la chaîne | |
| 12 | +- **Signal d'identité** : profil_source (0.98) — la chaîne est le compte d'origine de la fiche (colonne `accounts.signal` / `confidence`) | |
| 13 | +- **Module** : `creaka/connectors/youtube_recherche.py` — classe `YouTubeRechercheConnector` (`kind = "discovery"`) | |
| 14 | + | |
| 15 | +## Accès | |
| 16 | + | |
| 17 | +- **Accès (registre)** : requêtes directes (recherche publique YouTube filtre « chaînes », ytInitialData) — ~120 requêtes thématiques × marqueurs QC | |
| 18 | +- **Type d'accès (code)** : API JSON | |
| 19 | +- **Endpoint de base** : https://www.youtube.com/results | |
| 20 | +- **URLs du module** : https://www.youtube.com/results · https://www.youtube.com/youtubei/v1/search | |
| 21 | +- **Pagination** : curseur / continuation | |
| 22 | +- **Backend anti-bot / rendu** : requests direct (session UA CreaKaBot, throttling poli) | |
| 23 | +- **Politesse** : 1.0 s entre requêtes, timeout 30 s, UA `CreaKaBot/1.0 (+https://www.crea-ka.com/bot)` | |
| 24 | +- **Authentification** : aucune — contenu public / API anonyme | |
| 25 | + | |
| 26 | +## Champs récupérés → schéma cible | |
| 27 | + | |
| 28 | +Source de **découverte** : produit des fiches `Creator` (tables `creators` + `accounts`). Complétude mesurée sur les 4595 créateurs découverts par cette source (champ `doc.source`) ; exemple tiré d'une fiche réelle. | |
| 29 | + | |
| 30 | +| Champ du doc créateur | Contenu | Renseigné | Exemple réel | | |
| 31 | +|---|---|---|---| | |
| 32 | +| `display_name` | Nom public | 100 % | Ça commence aujourd'hui - France Télévisions | | |
| 33 | +| `bio` | Bio | 88 % | La vérité plus qu'ailleurs avec Faustine Bollaert. | | |
| 34 | +| `region` | Région | 0 % | — | | |
| 35 | +| `city` | Ville | 0 % | — | | |
| 36 | +| `niches` | Niches (liste) | 100 % | sport-fitness | | |
| 37 | +| `languages` | Langues (liste) | 100 % | fr | | |
| 38 | +| `creator_type` | Type de créateur | 100 % | youtubeur | | |
| 39 | +| `primary_platform` | Plateforme principale | 100 % | youtube | | |
| 40 | +| `platforms` | Comptes sociaux (liste) | 100 % | youtube:@%c3%87acommenceaujourdhui-francet%c3%a9l | | |
| 41 | +| `avatar_url` | Avatar | 90 % | https://yt3.ggpht.com/34WaPmV58PCfc9W7IvR_oAVkTcQrRyYMenz4phwmrE7LPm8f4zJfOIc2Jq4XkMZZsYj… | | |
| 42 | +| `total_reach` | Portée totale (abonnés cumulés) | 96 % | 1860000 | | |
| 43 | +| `link_in_bio_url` | Lien-en-bio | 0 % | — | | |
| 44 | + | |
| 45 | +Cible d'**enrichissement** : colonnes de la table `accounts` pour la/les plateforme(s) `youtube` (4768 comptes en BD, confiance moyenne 0.98). | |
| 46 | + | |
| 47 | +| Colonne `accounts` | Contenu | Renseignée | Exemple réel | | |
| 48 | +|---|---|---|---| | |
| 49 | +| `handle` | Handle | 100 % | nilered | | |
| 50 | +| `url` | URL du profil | 100 % | https://www.youtube.com/@nilered | | |
| 51 | +| `followers` | Abonnés | 93 % | 10900000 | | |
| 52 | +| `verified` | Badge vérifié | 0 % | — | | |
| 53 | +| `confidence` | Confiance d'identité | 100 % | 0.9 | | |
| 54 | +| `signal` | Signal d'identité | 100 % | cross_link | | |
| 55 | +| `metrics` | Métriques détaillées (JSON) | 0 % | — | | |
| 56 | +| `last_checked` | Dernière vérification | 100 % | 2026-08-18T00:32:23Z | | |
| 57 | + | |
| 58 | +## Fréquence & budget | |
| 59 | + | |
| 60 | +- **Cadence déclarée (registre)** : quotidienne (watch) — la couverture s'accumule à chaque passage | |
| 61 | +- **Cadence observée** (médiane sync_log) : ≈ 5.3 h | |
| 62 | +- **Dernier passage** : 2026-08-18T07:22:54Z — 4155 créateurs, 4155 comptes, +555 / ~3600, 0 erreur(s), 3304 s | |
| 63 | +- **Throttling** : 1.0 s entre requêtes (`request_delay`) | |
| 64 | + | |
| 65 | +## Volumétrie & complétude | |
| 66 | + | |
| 67 | +- **Créateurs découverts par la source** (`doc.source`) : 4595 · **fiches touchées** (`source_ids`) : 4595 | |
| 68 | +- **Complétude clé (découverts)** : niches 100 % · avatar 90 % · portée 96 % · région 0 % | |
| 69 | +- **Comptes `youtube` en BD** : 4768 — abonnés 93 % · métriques 0 % · dernière vérification 2026-08-18T07:22:54Z | |
| 70 | +- **Runs journalisés (60 derniers)** : 3, dont 0 avec erreurs | |
| 71 | + | |
| 72 | +## Erreurs connues & dépannage | |
| 73 | + | |
| 74 | +Aucune erreur ni alerte dans les 60 derniers runs journalisés. | |
| 75 | + | |
| 76 | +Rejouer la source seule : `python3 run.py sync youtube-recherche` · vérifier `sync_log` (`SELECT * FROM sync_log WHERE source='youtube-recherche' ORDER BY ts DESC LIMIT 5;`). | |
| 77 | + | |
| 78 | +## Licence, attribution & conditions | |
| 79 | + | |
| 80 | +- **Cadre d'accès (registre)** : requêtes directes (recherche publique YouTube filtre « chaînes », ytInitialData) — ~120 requêtes thématiques × marqueurs QC | |
| 81 | +- **Profils publics uniquement** : UA identifiable `CreaKaBot/1.0 (+https://www.crea-ka.com/bot; contact@spboucher.ai)`, throttling poli, respect des opt-out (`data/optout.json`) et du volet éthique (`creaka/ethics.py` — mineurs exclus via `is_minor`). | |
| 82 | +- Retrait sur demande : contact@spboucher.ai (opt-out honoré à la prochaine ingestion). | |
| 83 | + | |
| 84 | +## Historique | |
| 85 | + | |
| 86 | +- 2026-08-17 — plus ancien passage journalisé dans `sync_log` (fenêtre des 60 derniers). | |
| 87 | +- 2026-08-18 — vague d'enrichissement : standardisation de la documentation des connecteurs (fiche générée par `scripts/gen_connector_docs.py`). | |
added
docs/connecteurs/youtube.md
+69 −0
@@ -0,0 +1,69 @@ | ||
| 1 | +# `youtube` — connecteur enrichissement (palier 2) | |
| 2 | + | |
| 3 | +_Fiche générée automatiquement par `scripts/gen_connector_docs.py` le 2026-08-18 03:30 — ne pas éditer à la main, régénérer._ | |
| 4 | + | |
| 5 | +**État : actif** · Famille : enrichissement (enrichment) · Backend : direct · Créateurs découverts : 0 · touchés : 0 | |
| 6 | + | |
| 7 | +## Description de la source | |
| 8 | + | |
| 9 | +Connecteur ENRICHISSEMENT YouTube — abonnés + badge par créateur. Mode d'accès : YouTube Data API v3 (OFFICIELLE, privilégiée §10) si YOUTUBE_API_KEY est définie ; sinon repli page publique /@handle (requêtes directes polies). Palier 2 du catalogue (§9). | |
| 10 | + | |
| 11 | +- **Notes (registre)** : abonnés par chaîne | |
| 12 | +- **Signal d'identité** : api_officielle (0.95) quand confirmé par l'API (colonne `accounts.signal` / `confidence`) | |
| 13 | +- **Module** : `creaka/connectors/youtube.py` — classe `YouTubeConnector` (`kind = "enrichment"`) | |
| 14 | + | |
| 15 | +## Accès | |
| 16 | + | |
| 17 | +- **Accès (registre)** : YouTube Data API v3 (officielle) si YOUTUBE_API_KEY, sinon page publique /@handle | |
| 18 | +- **Type d'accès (code)** : API JSON | |
| 19 | +- **Endpoint de base** : https://www.googleapis.com/youtube/v3/channels?part=statistics,snippet&forHandle={h}&key={key} | |
| 20 | +- **URLs du module** : https://www.googleapis.com/youtube/v3/channels · https://www.youtube.com/@{h} | |
| 21 | +- **Pagination** : réponse unique (pas de pagination) | |
| 22 | +- **Backend anti-bot / rendu** : requests direct (session UA CreaKaBot, throttling poli) | |
| 23 | +- **Politesse** : 1.0 s entre requêtes, timeout 30 s, UA `CreaKaBot/1.0 (+https://www.crea-ka.com/bot)` | |
| 24 | +- **Authentification** : aucune — contenu public / API anonyme | |
| 25 | + | |
| 26 | +## Champs récupérés → schéma cible | |
| 27 | + | |
| 28 | +Cible d'**enrichissement** : colonnes de la table `accounts` pour la/les plateforme(s) `youtube` (4768 comptes en BD, confiance moyenne 0.98). | |
| 29 | + | |
| 30 | +| Colonne `accounts` | Contenu | Renseignée | Exemple réel | | |
| 31 | +|---|---|---|---| | |
| 32 | +| `handle` | Handle | 100 % | nilered | | |
| 33 | +| `url` | URL du profil | 100 % | https://www.youtube.com/@nilered | | |
| 34 | +| `followers` | Abonnés | 93 % | 10900000 | | |
| 35 | +| `verified` | Badge vérifié | 0 % | — | | |
| 36 | +| `confidence` | Confiance d'identité | 100 % | 0.9 | | |
| 37 | +| `signal` | Signal d'identité | 100 % | cross_link | | |
| 38 | +| `metrics` | Métriques détaillées (JSON) | 0 % | — | | |
| 39 | +| `last_checked` | Dernière vérification | 100 % | 2026-08-18T00:32:23Z | | |
| 40 | + | |
| 41 | +## Fréquence & budget | |
| 42 | + | |
| 43 | +- **Cadence déclarée (registre)** : hebdomadaire | |
| 44 | +- **Cadence observée** (médiane sync_log) : — | |
| 45 | +- **Dernier passage** : 2026-08-18T01:06:18Z — 1487 créateurs, 1576 comptes, +0 / ~1487, 0 erreur(s), 0 s | |
| 46 | +- **Throttling** : 1.0 s entre requêtes (`request_delay`) | |
| 47 | + | |
| 48 | +## Volumétrie & complétude | |
| 49 | + | |
| 50 | +- **Créateurs découverts par la source** (`doc.source`) : 0 · **fiches touchées** (`source_ids`) : 0 | |
| 51 | +- **Comptes `youtube` en BD** : 4768 — abonnés 93 % · métriques 0 % · dernière vérification 2026-08-18T07:22:54Z | |
| 52 | +- **Runs journalisés (60 derniers)** : 1, dont 0 avec erreurs | |
| 53 | + | |
| 54 | +## Erreurs connues & dépannage | |
| 55 | + | |
| 56 | +Aucune erreur ni alerte dans les 60 derniers runs journalisés. | |
| 57 | + | |
| 58 | +Rejouer la source seule : `python3 run.py sync youtube` · vérifier `sync_log` (`SELECT * FROM sync_log WHERE source='youtube' ORDER BY ts DESC LIMIT 5;`). | |
| 59 | + | |
| 60 | +## Licence, attribution & conditions | |
| 61 | + | |
| 62 | +- **Cadre d'accès (registre)** : YouTube Data API v3 (officielle) si YOUTUBE_API_KEY, sinon page publique /@handle | |
| 63 | +- **API publique** utilisée selon ses conditions (pas de clé détournée, throttling poli) ; données limitées aux profils publics. | |
| 64 | +- Retrait sur demande : contact@spboucher.ai (opt-out honoré à la prochaine ingestion). | |
| 65 | + | |
| 66 | +## Historique | |
| 67 | + | |
| 68 | +- 2026-08-18 — plus ancien passage journalisé dans `sync_log` (fenêtre des 60 derniers). | |
| 69 | +- 2026-08-18 — vague d'enrichissement : standardisation de la documentation des connecteurs (fiche générée par `scripts/gen_connector_docs.py`). | |
added
scripts/gen_connector_docs.py
+557 −0
@@ -0,0 +1,557 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +# ============================================================================== | |
| 3 | +# Author: Simon-Pierre Boucher <contact@spboucher.ai> | |
| 4 | +# File: scripts/gen_connector_docs.py | |
| 5 | +# Desc: Documentation STANDARDISÉE des connecteurs Créa-Ka — génère | |
| 6 | +# docs/connecteurs/INDEX.md + une fiche docs/connecteurs/<id>.md par | |
| 7 | +# source du registre, de façon 100 % programmatique et rejouable : | |
| 8 | +# 1. registre data/sources.json (famille, palier, accès, cadence, | |
| 9 | +# signal d'identité, notes, statut) ; | |
| 10 | +# 2. introspection STATIQUE (ast) de creaka/connectors/*.py : | |
| 11 | +# classe, kind (discovery/enrichment), backend, endpoints, | |
| 12 | +# pagination, constantes de budget — sans exécuter le code ; | |
| 13 | +# 3. BD live data/creaka.db : créateurs découverts/enrichis par | |
| 14 | +# source (doc JSON), complétude des champs, comptes par | |
| 15 | +# plateforme, dernier sync, cadence observée, alertes récentes. | |
| 16 | +# Usage : python3 scripts/gen_connector_docs.py (racine du projet) | |
| 17 | +# ============================================================================== | |
| 18 | +from __future__ import annotations | |
| 19 | + | |
| 20 | +import ast | |
| 21 | +import json | |
| 22 | +import re | |
| 23 | +import sqlite3 | |
| 24 | +import statistics | |
| 25 | +from datetime import datetime, timezone | |
| 26 | +from pathlib import Path | |
| 27 | + | |
| 28 | +ROOT = Path(__file__).resolve().parents[1] | |
| 29 | +CONN_DIR = ROOT / "creaka" / "connectors" | |
| 30 | +DOCS_DIR = ROOT / "docs" / "connecteurs" | |
| 31 | +DB_PATH = ROOT / "data" / "creaka.db" | |
| 32 | +SOURCES_JSON = ROOT / "data" / "sources.json" | |
| 33 | + | |
| 34 | +SKIP_MODULES = {"__init__", "base"} | |
| 35 | +URL_RE = re.compile(r"https?://[^\s\"'\\)>,;]+") | |
| 36 | +DATE_RE = re.compile(r"\d{4}-\d{2}-\d{2}") | |
| 37 | +INFRA_HOSTS = ("api.firecrawl.dev", "api.scrapfly.io", "crea-ka.com") | |
| 38 | + | |
| 39 | +# plateformes de la table `accounts` alimentées par chaque source d'enrichissement | |
| 40 | +SOURCE_PLATFORMS = { | |
| 41 | + "instagram-profil": ("instagram",), | |
| 42 | + "tiktok-profil": ("tiktok",), | |
| 43 | + "youtube": ("youtube",), | |
| 44 | + "youtube-recherche": ("youtube",), | |
| 45 | + "twitch": ("twitch",), | |
| 46 | + "x-profil": ("x",), | |
| 47 | + "balados-rss": ("podcast",), | |
| 48 | + "balados-itunes": ("podcast",), | |
| 49 | + "podcastindex": ("podcast",), | |
| 50 | + "onlyqueb": ("onlyfans",), | |
| 51 | +} | |
| 52 | + | |
| 53 | +# champs du doc créateur documentés (complétude par source de découverte) | |
| 54 | +DOC_FIELDS = [ | |
| 55 | + ("display_name", "Nom public"), | |
| 56 | + ("bio", "Bio"), | |
| 57 | + ("region", "Région"), | |
| 58 | + ("city", "Ville"), | |
| 59 | + ("niches", "Niches (liste)"), | |
| 60 | + ("languages", "Langues (liste)"), | |
| 61 | + ("creator_type", "Type de créateur"), | |
| 62 | + ("primary_platform", "Plateforme principale"), | |
| 63 | + ("platforms", "Comptes sociaux (liste)"), | |
| 64 | + ("avatar_url", "Avatar"), | |
| 65 | + ("total_reach", "Portée totale (abonnés cumulés)"), | |
| 66 | + ("link_in_bio_url", "Lien-en-bio"), | |
| 67 | +] | |
| 68 | + | |
| 69 | +ACC_FIELDS = [ | |
| 70 | + ("handle", "Handle", "handle IS NOT NULL AND handle != ''"), | |
| 71 | + ("url", "URL du profil", "url IS NOT NULL AND url != ''"), | |
| 72 | + ("followers", "Abonnés", "followers IS NOT NULL"), | |
| 73 | + ("verified", "Badge vérifié", "verified IS NOT NULL"), | |
| 74 | + ("confidence", "Confiance d'identité", "confidence IS NOT NULL"), | |
| 75 | + ("signal", "Signal d'identité", "signal IS NOT NULL AND signal != ''"), | |
| 76 | + ("metrics", "Métriques détaillées (JSON)", | |
| 77 | + "metrics IS NOT NULL AND length(metrics) > 4"), | |
| 78 | + ("last_checked", "Dernière vérification", | |
| 79 | + "last_checked IS NOT NULL AND last_checked != ''"), | |
| 80 | +] | |
| 81 | + | |
| 82 | + | |
| 83 | +def esc(s, limit: int = 100) -> str: | |
| 84 | + s = str(s).replace("\\", "\\\\").replace("|", "\\|") | |
| 85 | + s = re.sub(r"\s+", " ", s).strip() | |
| 86 | + return s[: limit - 1] + "…" if len(s) > limit else s | |
| 87 | + | |
| 88 | + | |
| 89 | +def pct(n, d) -> str: | |
| 90 | + return f"{100.0 * (n or 0) / d:.0f} %" if d else "—" | |
| 91 | + | |
| 92 | + | |
| 93 | +def parse_iso(ts: str) -> float | None: | |
| 94 | + try: | |
| 95 | + return datetime.fromisoformat(ts.replace("Z", "+00:00")).timestamp() | |
| 96 | + except (ValueError, AttributeError): | |
| 97 | + return None | |
| 98 | + | |
| 99 | + | |
| 100 | +def fmt_secs(sec: float) -> str: | |
| 101 | + if sec < 5400: | |
| 102 | + return f"≈ {sec / 60:.0f} min" | |
| 103 | + if sec < 129600: | |
| 104 | + return f"≈ {sec / 3600:.1f} h" | |
| 105 | + return f"≈ {sec / 86400:.1f} j" | |
| 106 | + | |
| 107 | + | |
| 108 | +def filled(v) -> bool: | |
| 109 | + return bool(v) | |
| 110 | + | |
| 111 | + | |
| 112 | +# -- introspection statique ------------------------------------------------------ | |
| 113 | + | |
| 114 | +def banner_description(text: str) -> str: | |
| 115 | + lines, started = [], False | |
| 116 | + for raw in text.splitlines(): | |
| 117 | + if not raw.startswith("#"): | |
| 118 | + if started: | |
| 119 | + break | |
| 120 | + continue | |
| 121 | + body = raw.lstrip("#").strip() | |
| 122 | + if set(body) <= {"-", "="}: | |
| 123 | + continue | |
| 124 | + if not started: | |
| 125 | + if body.startswith("Desc:"): | |
| 126 | + started = True | |
| 127 | + lines.append(body[len("Desc:"):].strip()) | |
| 128 | + continue | |
| 129 | + if re.match(r"^(Author|File):", body): | |
| 130 | + break | |
| 131 | + lines.append(body) | |
| 132 | + return " ".join(l for l in lines if l).strip() | |
| 133 | + | |
| 134 | + | |
| 135 | +def introspect_module(path: Path) -> list[dict]: | |
| 136 | + text = path.read_text(encoding="utf-8") | |
| 137 | + try: | |
| 138 | + tree = ast.parse(text) | |
| 139 | + except SyntaxError: | |
| 140 | + return [] | |
| 141 | + constants: dict = {} | |
| 142 | + for node in tree.body: | |
| 143 | + if isinstance(node, ast.Assign) and len(node.targets) == 1 \ | |
| 144 | + and isinstance(node.targets[0], ast.Name) \ | |
| 145 | + and node.targets[0].id.isupper(): | |
| 146 | + try: | |
| 147 | + constants[node.targets[0].id] = ast.literal_eval(node.value) | |
| 148 | + except (ValueError, TypeError, SyntaxError): | |
| 149 | + seg = ast.get_source_segment(text, node.value) or "" | |
| 150 | + urls = URL_RE.findall(seg) | |
| 151 | + constants[node.targets[0].id] = urls if len(urls) > 1 else \ | |
| 152 | + (urls[0] if urls else None) | |
| 153 | + urls_all = [] | |
| 154 | + for u in URL_RE.findall(text): | |
| 155 | + u = u.rstrip('".') | |
| 156 | + host = u.split("//", 1)[-1].split("/", 1)[0] | |
| 157 | + if "." not in host: # fragment de f-string, pas une vraie URL | |
| 158 | + continue | |
| 159 | + if not any(h in u for h in INFRA_HOSTS) and u not in urls_all: | |
| 160 | + urls_all.append(u) | |
| 161 | + endpoint = None | |
| 162 | + for name in ("BASE", "BASE_URL", "API", "API_URL", "API_BASE", "SPARQL", | |
| 163 | + "ENDPOINT", "ROOT", "URL", "SEARCH_URL", "LOOKUP_URL"): | |
| 164 | + v = constants.get(name) | |
| 165 | + if isinstance(v, str) and v.startswith("http"): | |
| 166 | + endpoint = v | |
| 167 | + break | |
| 168 | + if not endpoint and urls_all: | |
| 169 | + endpoint = urls_all[0] | |
| 170 | + budgets = {k: v for k, v in constants.items() | |
| 171 | + if isinstance(v, (int, float)) and not isinstance(v, bool) | |
| 172 | + and re.search(r"MAX|CAP|LIMIT|BUDGET|TTL|PER_PAGE|PAGES|DELAY|BATCH", | |
| 173 | + k)} | |
| 174 | + detailed, family = [], "direct" | |
| 175 | + if re.search(r"self\.(get|post)\(|requests\.(get|post)\(", text): | |
| 176 | + detailed.append("requests direct (session UA CreaKaBot, throttling poli)") | |
| 177 | + if ".scrapfly(" in text: | |
| 178 | + detailed.append("Scrapfly (asp + render_js — contournement anti-bot)") | |
| 179 | + family = "Scrapfly" | |
| 180 | + if "get_rendered(" in text: | |
| 181 | + detailed.append("Firecrawl (HTML rendu, JavaScript exécuté)") | |
| 182 | + family = "Firecrawl" if family == "direct" else family | |
| 183 | + if not detailed: | |
| 184 | + detailed.append("requests direct") | |
| 185 | + low = text.lower() | |
| 186 | + if "sparql" in low: | |
| 187 | + flavor = "API SPARQL publique (Wikidata)" | |
| 188 | + elif "graphql" in low: | |
| 189 | + flavor = "API GraphQL" | |
| 190 | + elif "itunes.apple.com" in low: | |
| 191 | + flavor = "API iTunes Search/Lookup (publique)" | |
| 192 | + elif "rss" in low and "feedparser" in low or "<rss" in low: | |
| 193 | + flavor = "flux RSS publics" | |
| 194 | + elif "sitemap" in low: | |
| 195 | + flavor = "sitemap XML + JSON-LD des pages profil (SSR)" | |
| 196 | + elif "json-ld" in low or "jsonld" in low or "profilepage" in low: | |
| 197 | + flavor = "JSON-LD des pages profil publiques" | |
| 198 | + elif re.search(r"\.json\(\)", text) and re.search(r"api[./_]", low): | |
| 199 | + flavor = "API JSON" | |
| 200 | + elif "dataset" in low or "seed" in low and "csv" in low: | |
| 201 | + flavor = "dataset local compilé (data/seed)" | |
| 202 | + else: | |
| 203 | + flavor = "pages HTML publiques" | |
| 204 | + hits = [] | |
| 205 | + if re.search(r"[?&]page=|[\"']page[\"']\s*[:=]", low): | |
| 206 | + hits.append("pagination par numéro de page") | |
| 207 | + if re.search(r"[?&]offset=|[\"']offset[\"']", low): | |
| 208 | + hits.append("pagination par offset") | |
| 209 | + if "cursor" in low or "continuation" in low: | |
| 210 | + hits.append("curseur / continuation") | |
| 211 | + if not hits: | |
| 212 | + hits.append("réponse unique (pas de pagination)") | |
| 213 | + base = {"module": path.stem, "path": f"creaka/connectors/{path.stem}.py", | |
| 214 | + "banner": banner_description(text), "endpoint": endpoint, | |
| 215 | + "urls": urls_all[:5], "budgets": budgets, "backends": detailed, | |
| 216 | + "backend_family": family, "flavor": flavor, | |
| 217 | + "pagination": " ; ".join(hits)} | |
| 218 | + entries = [] | |
| 219 | + for node in tree.body: | |
| 220 | + if not isinstance(node, ast.ClassDef): | |
| 221 | + continue | |
| 222 | + bases = {getattr(b, "id", getattr(b, "attr", "")) for b in node.bases} | |
| 223 | + if "BaseConnector" not in bases: | |
| 224 | + continue | |
| 225 | + attrs = {"request_delay": 0.8, "timeout": 30, "kind": "discovery", | |
| 226 | + "source_id": ""} | |
| 227 | + for sub in node.body: | |
| 228 | + if isinstance(sub, ast.Assign) and len(sub.targets) == 1 \ | |
| 229 | + and isinstance(sub.targets[0], ast.Name): | |
| 230 | + try: | |
| 231 | + attrs[sub.targets[0].id] = ast.literal_eval(sub.value) | |
| 232 | + except (ValueError, TypeError, SyntaxError): | |
| 233 | + pass | |
| 234 | + if attrs["source_id"]: | |
| 235 | + entries.append({**base, "class": node.name, | |
| 236 | + "class_doc": ast.get_docstring(node) or "", | |
| 237 | + **attrs}) | |
| 238 | + return entries | |
| 239 | + | |
| 240 | + | |
| 241 | +# -- BD live ---------------------------------------------------------------------- | |
| 242 | + | |
| 243 | +def load_creators(con) -> list[dict]: | |
| 244 | + out = [] | |
| 245 | + for (doc,) in con.execute("SELECT doc FROM creators"): | |
| 246 | + try: | |
| 247 | + out.append(json.loads(doc)) | |
| 248 | + except ValueError: | |
| 249 | + pass | |
| 250 | + return out | |
| 251 | + | |
| 252 | + | |
| 253 | +def db_stats(con, sid: str, creators: list[dict]) -> dict: | |
| 254 | + mine = [c for c in creators if c.get("source") == sid] | |
| 255 | + touched = sum(1 for c in creators | |
| 256 | + if sid in (c.get("source_ids") or []) or c.get("source") == sid) | |
| 257 | + doc_fill = {f: sum(1 for c in mine if filled(c.get(f))) | |
| 258 | + for f, _l in DOC_FIELDS} | |
| 259 | + sample = max(mine, key=lambda c: c.get("total_reach") or 0) if mine else None | |
| 260 | + | |
| 261 | + acc = None | |
| 262 | + acc_sample = None | |
| 263 | + platforms = SOURCE_PLATFORMS.get(sid) | |
| 264 | + if platforms: | |
| 265 | + ph = ",".join("?" * len(platforms)) | |
| 266 | + parts = ", ".join( | |
| 267 | + f"sum(CASE WHEN {cond} THEN 1 ELSE 0 END) AS f_{col}" | |
| 268 | + for col, _l, cond in ACC_FIELDS) | |
| 269 | + acc = con.execute( | |
| 270 | + f"SELECT count(*) AS n, avg(confidence) AS conf, " | |
| 271 | + f"max(last_checked) AS checked, {parts} FROM accounts " | |
| 272 | + f"WHERE platform IN ({ph})", platforms).fetchone() | |
| 273 | + acc_sample = con.execute( | |
| 274 | + f"SELECT * FROM accounts WHERE platform IN ({ph}) AND followers IS " | |
| 275 | + f"NOT NULL ORDER BY followers DESC LIMIT 1", platforms).fetchone() | |
| 276 | + | |
| 277 | + runs = con.execute( | |
| 278 | + "SELECT ts, creators, accounts, avg_confidence, added, updated, " | |
| 279 | + "errors, seconds, alert FROM sync_log WHERE source=? " | |
| 280 | + "ORDER BY ts DESC LIMIT 60", (sid,)).fetchall() | |
| 281 | + ok_ts = sorted(t for t in (parse_iso(r["ts"]) for r in runs) if t) | |
| 282 | + cadence = None | |
| 283 | + if len(ok_ts) >= 3: | |
| 284 | + deltas = [b - a for a, b in zip(ok_ts, ok_ts[1:]) if b - a > 60] | |
| 285 | + if deltas: | |
| 286 | + cadence = statistics.median(deltas) | |
| 287 | + alerts = [r for r in runs if (r["errors"] or 0) > 0 or r["alert"]][:5] | |
| 288 | + return {"mine": len(mine), "touched": touched, "doc_fill": doc_fill, | |
| 289 | + "sample": sample, "acc": acc, "acc_sample": acc_sample, | |
| 290 | + "platforms": platforms, "runs": runs, "cadence": cadence, | |
| 291 | + "last": runs[0] if runs else None, "alerts": alerts} | |
| 292 | + | |
| 293 | + | |
| 294 | +# -- rendu --------------------------------------------------------------------------- | |
| 295 | + | |
| 296 | +def render_fiche(reg: dict, entry: dict | None, st: dict, now: str) -> str: | |
| 297 | + sid = reg["id"] | |
| 298 | + etat = reg.get("statut", "actif") | |
| 299 | + fam = reg.get("famille", "—") | |
| 300 | + out = [f"# `{sid}` — connecteur {fam} (palier {reg.get('palier', '—')})", "", | |
| 301 | + f"_Fiche générée automatiquement par " | |
| 302 | + f"`scripts/gen_connector_docs.py` le {now} — ne pas éditer à la " | |
| 303 | + f"main, régénérer._", ""] | |
| 304 | + kind = entry.get("kind") if entry else fam | |
| 305 | + out.append(f"**État : {esc(etat, 80)}** · Famille : {fam} ({kind}) · " | |
| 306 | + f"Backend : {entry['backend_family'] if entry else '—'} · " | |
| 307 | + f"Créateurs découverts : {st['mine']} · touchés : " | |
| 308 | + f"{st['touched']}") | |
| 309 | + out.append("") | |
| 310 | + | |
| 311 | + out.append("## Description de la source") | |
| 312 | + out.append("") | |
| 313 | + if entry and entry["banner"]: | |
| 314 | + out.append(entry["banner"]) | |
| 315 | + out.append("") | |
| 316 | + out.append(f"- **Notes (registre)** : {reg.get('notes', '—')}") | |
| 317 | + out.append(f"- **Signal d'identité** : {reg.get('signal_identite', '—')} " | |
| 318 | + f"(colonne `accounts.signal` / `confidence`)") | |
| 319 | + if entry: | |
| 320 | + out.append(f"- **Module** : `{entry['path']}` — classe " | |
| 321 | + f"`{entry['class']}` (`kind = \"{entry['kind']}\"`)") | |
| 322 | + else: | |
| 323 | + out.append("- **Module** : introuvable (vérifier le registre)") | |
| 324 | + out.append("") | |
| 325 | + | |
| 326 | + out.append("## Accès") | |
| 327 | + out.append("") | |
| 328 | + out.append(f"- **Accès (registre)** : {reg.get('acces', '—')}") | |
| 329 | + if entry: | |
| 330 | + out.append(f"- **Type d'accès (code)** : {entry['flavor']}") | |
| 331 | + out.append(f"- **Endpoint de base** : {entry['endpoint'] or '—'}") | |
| 332 | + if entry["urls"]: | |
| 333 | + out.append("- **URLs du module** : " + " · ".join(entry["urls"][:4])) | |
| 334 | + out.append(f"- **Pagination** : {entry['pagination']}") | |
| 335 | + out.append(f"- **Backend anti-bot / rendu** : " | |
| 336 | + f"{' ; '.join(entry['backends'])}") | |
| 337 | + out.append(f"- **Politesse** : {entry['request_delay']} s entre " | |
| 338 | + f"requêtes, timeout {entry['timeout']} s, UA " | |
| 339 | + f"`CreaKaBot/1.0 (+https://www.crea-ka.com/bot)`") | |
| 340 | + needs_key = "clé" in (reg.get("acces") or "").lower() \ | |
| 341 | + or "clé" in str(etat).lower() | |
| 342 | + auth = ("clé(s) API requise(s) — voir statut/registre" if needs_key | |
| 343 | + else "aucune — contenu public / API anonyme") | |
| 344 | + out.append(f"- **Authentification** : {auth}") | |
| 345 | + out.append("") | |
| 346 | + | |
| 347 | + out.append("## Champs récupérés → schéma cible") | |
| 348 | + out.append("") | |
| 349 | + if st["mine"]: | |
| 350 | + out.append(f"Source de **découverte** : produit des fiches `Creator` " | |
| 351 | + f"(tables `creators` + `accounts`). Complétude mesurée sur " | |
| 352 | + f"les {st['mine']} créateurs découverts par cette source " | |
| 353 | + f"(champ `doc.source`) ; exemple tiré d'une fiche réelle.") | |
| 354 | + out.append("") | |
| 355 | + out.append("| Champ du doc créateur | Contenu | Renseigné | Exemple réel |") | |
| 356 | + out.append("|---|---|---|---|") | |
| 357 | + s = st["sample"] or {} | |
| 358 | + for f, label in DOC_FIELDS: | |
| 359 | + v = s.get(f) | |
| 360 | + if f == "platforms" and v: | |
| 361 | + ex = esc(", ".join(f"{p.get('platform')}:@{p.get('handle')}" | |
| 362 | + for p in v[:3]), 90) | |
| 363 | + elif isinstance(v, list): | |
| 364 | + ex = esc(", ".join(map(str, v[:4])), 90) | |
| 365 | + else: | |
| 366 | + ex = esc(v, 90) if filled(v) else "—" | |
| 367 | + out.append(f"| `{f}` | {label} | " | |
| 368 | + f"{pct(st['doc_fill'][f], st['mine'])} | {ex} |") | |
| 369 | + out.append("") | |
| 370 | + if st["acc"] is not None and st["acc"]["n"]: | |
| 371 | + a = st["acc"] | |
| 372 | + plats = ", ".join(st["platforms"]) | |
| 373 | + out.append(f"Cible d'**enrichissement** : colonnes de la table " | |
| 374 | + f"`accounts` pour la/les plateforme(s) `{plats}` " | |
| 375 | + f"({a['n']} comptes en BD, confiance moyenne " | |
| 376 | + f"{a['conf']:.2f}).") | |
| 377 | + out.append("") | |
| 378 | + out.append("| Colonne `accounts` | Contenu | Renseignée | Exemple réel |") | |
| 379 | + out.append("|---|---|---|---|") | |
| 380 | + sm = st["acc_sample"] | |
| 381 | + for col, label, _c in ACC_FIELDS: | |
| 382 | + ex = esc(sm[col], 90) if sm is not None and sm[col] not in ( | |
| 383 | + None, "") else "—" | |
| 384 | + out.append(f"| `{col}` | {label} | {pct(a[f'f_{col}'], a['n'])} " | |
| 385 | + f"| {ex} |") | |
| 386 | + out.append("") | |
| 387 | + if not st["mine"] and (st["acc"] is None or not st["acc"]["n"]): | |
| 388 | + out.append("Aucune donnée attribuable à cette source en BD pour " | |
| 389 | + "l'instant (clés manquantes ou source en attente) — schéma " | |
| 390 | + "cible : `creators` + `accounts`.") | |
| 391 | + out.append("") | |
| 392 | + | |
| 393 | + out.append("## Fréquence & budget") | |
| 394 | + out.append("") | |
| 395 | + out.append(f"- **Cadence déclarée (registre)** : {reg.get('cadence', '—')}") | |
| 396 | + cad = fmt_secs(st["cadence"]) if st["cadence"] else "—" | |
| 397 | + out.append(f"- **Cadence observée** (médiane sync_log) : {cad}") | |
| 398 | + last = st["last"] | |
| 399 | + if last: | |
| 400 | + out.append(f"- **Dernier passage** : {esc(last['ts'], 20)} — " | |
| 401 | + f"{last['creators'] or 0} créateurs, {last['accounts'] or 0} " | |
| 402 | + f"comptes, +{last['added'] or 0} / ~{last['updated'] or 0}, " | |
| 403 | + f"{last['errors'] or 0} erreur(s), " | |
| 404 | + f"{(last['seconds'] or 0):.0f} s") | |
| 405 | + if entry and entry["budgets"]: | |
| 406 | + caps = ", ".join(f"`{k}` = {v}" for k, v in sorted(entry["budgets"].items())) | |
| 407 | + out.append(f"- **Caps / budgets du module** : {caps}") | |
| 408 | + if entry: | |
| 409 | + out.append(f"- **Throttling** : {entry['request_delay']} s entre " | |
| 410 | + f"requêtes (`request_delay`)") | |
| 411 | + out.append("") | |
| 412 | + | |
| 413 | + out.append("## Volumétrie & complétude") | |
| 414 | + out.append("") | |
| 415 | + out.append(f"- **Créateurs découverts par la source** (`doc.source`) : " | |
| 416 | + f"{st['mine']} · **fiches touchées** (`source_ids`) : " | |
| 417 | + f"{st['touched']}") | |
| 418 | + if st["mine"]: | |
| 419 | + out.append(f"- **Complétude clé (découverts)** : niches " | |
| 420 | + f"{pct(st['doc_fill']['niches'], st['mine'])} · avatar " | |
| 421 | + f"{pct(st['doc_fill']['avatar_url'], st['mine'])} · portée " | |
| 422 | + f"{pct(st['doc_fill']['total_reach'], st['mine'])} · région " | |
| 423 | + f"{pct(st['doc_fill']['region'], st['mine'])}") | |
| 424 | + if st["acc"] is not None and st["acc"]["n"]: | |
| 425 | + a = st["acc"] | |
| 426 | + out.append(f"- **Comptes `{', '.join(st['platforms'])}` en BD** : " | |
| 427 | + f"{a['n']} — abonnés {pct(a['f_followers'], a['n'])} · " | |
| 428 | + f"métriques {pct(a['f_metrics'], a['n'])} · dernière " | |
| 429 | + f"vérification {esc(a['checked'] or '—', 20)}") | |
| 430 | + out.append(f"- **Runs journalisés (60 derniers)** : {len(st['runs'])}, " | |
| 431 | + f"dont {sum(1 for r in st['runs'] if (r['errors'] or 0) > 0)} " | |
| 432 | + f"avec erreurs") | |
| 433 | + out.append("") | |
| 434 | + | |
| 435 | + out.append("## Erreurs connues & dépannage") | |
| 436 | + out.append("") | |
| 437 | + if st["alerts"]: | |
| 438 | + out.append("| Passage | Erreurs | Alerte (sync_log) |") | |
| 439 | + out.append("|---|---|---|") | |
| 440 | + for r in st["alerts"]: | |
| 441 | + out.append(f"| {esc(r['ts'], 20)} | {r['errors'] or 0} | " | |
| 442 | + f"{esc(r['alert'] or '—', 160)} |") | |
| 443 | + out.append("") | |
| 444 | + else: | |
| 445 | + out.append("Aucune erreur ni alerte dans les 60 derniers runs " | |
| 446 | + "journalisés.") | |
| 447 | + out.append("") | |
| 448 | + if str(etat) != "actif": | |
| 449 | + out.append(f"**Statut du registre** : {etat}") | |
| 450 | + out.append("") | |
| 451 | + out.append(f"Rejouer la source seule : `python3 run.py sync {sid}` · " | |
| 452 | + f"vérifier `sync_log` (`SELECT * FROM sync_log WHERE " | |
| 453 | + f"source='{sid}' ORDER BY ts DESC LIMIT 5;`).") | |
| 454 | + out.append("") | |
| 455 | + | |
| 456 | + out.append("## Licence, attribution & conditions") | |
| 457 | + out.append("") | |
| 458 | + out.append(f"- **Cadre d'accès (registre)** : {reg.get('acces', '—')}") | |
| 459 | + low = (reg.get("acces") or "").lower() | |
| 460 | + if "wikidata" in low or "sparql" in low: | |
| 461 | + out.append("- **Licence** : données Wikidata sous CC0 — réutilisation " | |
| 462 | + "libre, mention « Source : Wikidata » affichée par " | |
| 463 | + "courtoisie.") | |
| 464 | + elif "dataset local" in low or "listes" in sid: | |
| 465 | + out.append("- **Données compilées** de listes et palmarès médias " | |
| 466 | + "**publics** ; seuls des faits publics (nom, handle, " | |
| 467 | + "audience approximative) sont conservés.") | |
| 468 | + elif "api" in low and ("publique" in low or "itunes" in low): | |
| 469 | + out.append("- **API publique** utilisée selon ses conditions (pas de " | |
| 470 | + "clé détournée, throttling poli) ; données limitées aux " | |
| 471 | + "profils publics.") | |
| 472 | + else: | |
| 473 | + out.append("- **Profils publics uniquement** : UA identifiable " | |
| 474 | + "`CreaKaBot/1.0 (+https://www.crea-ka.com/bot; " | |
| 475 | + "contact@spboucher.ai)`, throttling poli, respect des " | |
| 476 | + "opt-out (`data/optout.json`) et du volet éthique " | |
| 477 | + "(`creaka/ethics.py` — mineurs exclus via `is_minor`).") | |
| 478 | + out.append("- Retrait sur demande : contact@spboucher.ai (opt-out honoré " | |
| 479 | + "à la prochaine ingestion).") | |
| 480 | + out.append("") | |
| 481 | + | |
| 482 | + out.append("## Historique") | |
| 483 | + out.append("") | |
| 484 | + blob = " ".join(str(reg.get(k, "")) for k in ("notes", "statut", "acces")) | |
| 485 | + for d in sorted({m.group(0) for m in DATE_RE.finditer(blob)}): | |
| 486 | + out.append(f"- {d} — date mentionnée au registre (voir notes/statut).") | |
| 487 | + if st["runs"]: | |
| 488 | + first = st["runs"][-1]["ts"] | |
| 489 | + out.append(f"- {str(first)[:10]} — plus ancien passage journalisé dans " | |
| 490 | + f"`sync_log` (fenêtre des 60 derniers).") | |
| 491 | + out.append("- 2026-08-18 — vague d'enrichissement : standardisation de la " | |
| 492 | + "documentation des connecteurs (fiche générée par " | |
| 493 | + "`scripts/gen_connector_docs.py`).") | |
| 494 | + out.append("") | |
| 495 | + return "\n".join(out) | |
| 496 | + | |
| 497 | + | |
| 498 | +def main() -> None: | |
| 499 | + now = datetime.now().strftime("%Y-%m-%d %H:%M") | |
| 500 | + registry = json.loads(SOURCES_JSON.read_text(encoding="utf-8"))["sources"] | |
| 501 | + con = sqlite3.connect(DB_PATH) | |
| 502 | + con.row_factory = sqlite3.Row | |
| 503 | + creators = load_creators(con) | |
| 504 | + | |
| 505 | + by_sid: dict[str, dict] = {} | |
| 506 | + for path in sorted(CONN_DIR.glob("*.py")): | |
| 507 | + if path.stem in SKIP_MODULES: | |
| 508 | + continue | |
| 509 | + for e in introspect_module(path): | |
| 510 | + by_sid[e["source_id"]] = e | |
| 511 | + | |
| 512 | + DOCS_DIR.mkdir(parents=True, exist_ok=True) | |
| 513 | + for old in DOCS_DIR.glob("*.md"): | |
| 514 | + old.unlink() | |
| 515 | + | |
| 516 | + rows = [] | |
| 517 | + for reg in registry: | |
| 518 | + sid = reg["id"] | |
| 519 | + entry = by_sid.get(sid) | |
| 520 | + st = db_stats(con, sid, creators) | |
| 521 | + (DOCS_DIR / f"{sid}.md").write_text( | |
| 522 | + render_fiche(reg, entry, st, now), encoding="utf-8") | |
| 523 | + last = st["last"] | |
| 524 | + acc = st["acc"] | |
| 525 | + comptes = (f"{acc['n']} ({', '.join(st['platforms'])})" | |
| 526 | + if acc is not None and acc["n"] else "—") | |
| 527 | + rows.append( | |
| 528 | + f"| [`{sid}`]({sid}.md) | {reg.get('famille', '—')} " | |
| 529 | + f"| P{reg.get('palier', '—')} " | |
| 530 | + f"| {esc(entry['flavor'] if entry else '—', 42)} " | |
| 531 | + f"| {entry['backend_family'] if entry else '—'} " | |
| 532 | + f"| {st['mine']} | {comptes} " | |
| 533 | + f"| {esc(str(reg.get('statut', 'actif')).split('—')[0], 26)} " | |
| 534 | + f"| {esc(last['ts'], 17) if last else '—'} |") | |
| 535 | + | |
| 536 | + n_creators = con.execute("SELECT count(*) FROM creators").fetchone()[0] | |
| 537 | + n_accounts = con.execute("SELECT count(*) FROM accounts").fetchone()[0] | |
| 538 | + idx = [ | |
| 539 | + "# Créa-Ka — Index des connecteurs", "", | |
| 540 | + f"_Généré automatiquement par `scripts/gen_connector_docs.py` le {now} " | |
| 541 | + f"— ne pas éditer à la main, régénérer._", "", | |
| 542 | + f"**{len(registry)} sources au registre** · **{n_creators} créateurs** " | |
| 543 | + f"et **{n_accounts} comptes** en BD.", "", | |
| 544 | + "| Source | Famille | Palier | Type d'accès | Backend | Découverts " | |
| 545 | + "| Comptes (plateforme) | État | Dernier passage |", | |
| 546 | + "|---|---|---|---|---|---|---|---|---|", | |
| 547 | + ] | |
| 548 | + idx.extend(rows) | |
| 549 | + idx.append("") | |
| 550 | + (DOCS_DIR / "INDEX.md").write_text("\n".join(idx), encoding="utf-8") | |
| 551 | + con.close() | |
| 552 | + print(f"[gen_connector_docs] {len(registry)} fiches + INDEX.md écrits dans " | |
| 553 | + f"{DOCS_DIR}") | |
| 554 | + | |
| 555 | + | |
| 556 | +if __name__ == "__main__": | |
| 557 | + main() | |
| 558 | ||