SPB Git forge

spb/crea-ka

Public

Créa·Ka — annuaire public cross-plateforme des créateurs de contenu québécois (crea-ka.com)

52commits 1branches 0releases
11.3 MBsize
maindefault branch
19 days agolast push
Python 73.6% HTML 13.2% TypeScript 6% JavaScript 4.5% CSS 1.7% Dockerfile 0.6%

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>
Simon-Pierre Boucher committed 1 mo ago (Aug 18, 2026) parent 6d3e57d

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