Analyseur de Webinaires
Un outil qui transforme les exportations brutes de webinaires en deux types de réponses :
- Par webinaire — qui est venu, combien de temps ils sont restés, quand ils sont partis, qui ils étaient, ce qu'ils ont dit.
- Inter-webinaires — l'assistance augmente-t-elle, quel créneau et quel format fonctionnent, qui revient, et quels éloges et plaintes se répètent.
Cette compétence est neutre en termes de pile. Elle spécifie des contrats de données, des règles de validation, des calculs et une sémantique de graphiques. Implémentez-la dans la pile web de votre choix (Flask/Django/FastAPI + Postgres + n'importe quelle bibliothèque de graphiques ; Streamlit ; un notebook ; un outil BI). L'implémentation de référence à partir de laquelle ces instructions ont été extraites est une application Flask + PostgreSQL + ApexCharts.
Avant de construire quoi que ce soit : la conversation d'accueil
Ne partez pas du principe que c'est Zoom. Ne partez pas du principe que c'est YouTube. Le plus grand échec de cet outil est de coder en dur le format d'exportation d'une seule plateforme. L'utilisateur peut organiser des webinaires sur Teams, GoToWebinar, Livestorm, Demio, Webex, Hopin, Riverside, StreamYard, Crowdcast, BigMarker ou une pile personnalisée, et peut héberger les rediffusions sur Vimeo, Wistia, son propre CDN, ou nulle part.
Exécutez cette séquence avant d'écrire du code :
Étape 1 — Demandez quelles plateformes sont en jeu
Demandez, sous forme de question à choix multiples courte :
- Sur quelle plateforme organisez-vous des webinaires en direct ? (Zoom Webinars / Zoom Meetings / Microsoft Teams / GoToWebinar / Livestorm / Demio / Webex / Hopin / BigMarker / Crowdcast / autre)
- Où se trouvent les enquêtes post-webinaire ? (intégrées à la plateforme de webinaire / Typeform / Google Forms / SurveyMonkey / Hubspot / aucune)
- Où se trouvent les rediffusions, le cas échéant ? (YouTube / Vimeo / Wistia / auto-hébergé / page d'atterrissage avec accès restreint / pas de rediffusions)
- Disposez-vous de données d'inscription distinctes des données de présence ? (un seul export combiné / deux exports séparés / inscription uniquement / présence uniquement)
Étape 2 — Demandez un véritable export, avant de concevoir quoi que ce soit
Dites clairement : « Téléchargez un véritable export de chaque source — participants, enquête, rediffusion — avec les données personnelles déjà supprimées. Je lirai les en-têtes de colonnes réels au lieu de deviner, et je vous dirai quelles parties du tableau de bord vos données peuvent et ne peuvent pas prendre en charge. »
Insistez là-dessus. Un seul fichier réel supprime toutes les hypothèses. Si l'utilisateur ne peut pas partager de données réelles, demandez-lui de coller la ligne d'en-tête plus 2 à 3 lignes entièrement fabriquées.
Étape 3 — Exécutez le contrôle de confidentialité sur le fichier téléchargé
Voir Contrôle de confidentialité. Rejetez le fichier s'il contient des données personnelles non anonymisées. Ne passez pas au mappage tant qu'un fichier propre n'est pas fourni.
Étape 4 — Mappez leurs colonnes au schéma canonique
Montrez à l'utilisateur un tableau de mappage explicite (leur colonne → champ canonique → ce qu'il débloque), et nommez ce qui manque. Voir Exigences de champs et references/platform-exports.md.
Étape 5 — Dites-leur quels graphiques ils obtiennent
Affichez un rapport de disponibilité des fonctionnalités avant de construire :
✅ Disponible avec vos données
Distribution du temps de visionnage, chronologie des spectateurs simultanés, détection des abandons,
entonnoir de présence, croissance mois par mois, nouveaux vs retours.
⚠️ Partiellement disponible
Données démographiques — vous avez le Pays mais pas le Titre de poste, donc le donut de
séniorité sera masqué.
❌ Non disponible
Analyses des rediffusions — pas d'export de rediffusion. L'onglet Rediffusions sera masqué
jusqu'à ce que vous en ajoutiez un (ou vous pouvez saisir les chiffres manuellement via le modèle CSV).
Ne construisez jamais un graphique dont les données d'entrée n'existent pas. Masquez tout le panneau et affichez une explication d'une ligne sur les données qui l'activeraient.
Contrôle de confidentialité (OBLIGATOIRE)
Chaque fichier téléchargé passe un contrôle d'anonymisation avant l'analyse, avant le stockage, avant l'analyse proprement dite. Si des données personnelles sont trouvées, rejetez l'intégralité du fichier, nommez les colonnes et numéros de ligne incriminés, et expliquez comment les anonymiser. N'importez jamais partiellement. N'anonymisez jamais automatiquement en silence.
Ce qui est considéré comme des données personnelles
| Catégorie | Détection |
|---|---|
| Adresses e-mail | [A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,} dans n'importe quelle cellule |
| Numéros de téléphone | (?:\+?\d[\s\-\(\)\.]{0,2}){7,15} avec ≥7 chiffres, à l'exclusion des horodatages/ID purs |
| Adresses postales | Cellule correspondant à des mots-clés de type rue (`street |
| Codes postaux / ZIP | \b\d{5}(-\d{4})?\b ou [A-Z]{1,2}\d[A-Z\d]? ?\d[A-Z]{2} lorsque le nom de colonne suggère un lieu |
| Adresses IP | Littéral IPv4/IPv6 |
| Identifiants gouvernementaux / de paiement | \b\d{3}-\d{2}-\d{4}\b (forme SSN), \b(?:\d[ -]*?){13,16}\b réussissant un contrôle Luhn |
| Noms de colonnes suspects | En-tête correspondant à `email |
Règles
- Analyse d'en-tête d'abord. Toute colonne dont le nom correspond à la liste suspecte est signalée même si les cellules échantillonnées semblent vides — une colonne vide aujourd'hui peut être remplie demain.
- Analysez chaque cellule de chaque ligne. Pas un échantillon. Les fichiers sont petits (des milliers de lignes) ; une analyse complète est peu coûteuse et une analyse par échantillon donne une fausse assurance.
- Les réponses d'enquête en texte libre sont également analysées. Les gens tapent « envoyez-moi un e-mail à [email protected] » dans les boîtes de commentaires. C'est la fuite la plus souvent manquée.
- Rejetez dès la première catégorie trouvée, mais signalez-les toutes. Terminez l'analyse et présentez tous les problèmes à la fois afin que l'utilisateur corrige le fichier en une seule fois.
- N'écrivez jamais un fichier rejeté sur le disque ou dans la base de données. Conservez-le en mémoire, rejetez-le, jetez-le.
- Enregistrez le rejet sans les valeurs incriminées. Enregistrez uniquement les noms de colonnes et les numéros de ligne — jamais l'e-mail ou le numéro de téléphone détecté lui-même.
Identifiants autorisés (ceux-ci NE sont PAS des rejets)
- Un ID de personne pseudo-anonyme stable — un hachage, un UUID ou un entier séquentiel cohérent entre les fichiers. Ceci est obligatoire pour l'analyse inter-webinaires (voir ci-dessous).
- Noms de pays / régions, noms d'organisations, titres de poste, niveaux de séniorité.
- Horodatages, durées, ID de webinaires, chaînes de sujets.
Modèle de message de rejet
❌ Téléchargement rejeté — données personnelles détectées dans attendees_march.csv
Colonne "Email" → 412 adresses e-mail (lignes 2–413)
Colonne "Contact Number" → 88 numéros de téléphone (lignes 2, 5, 9, …)
Colonne "Que pouvons-nous améliorer ?" → 3 adresses e-mail dans du texte libre
(lignes 47, 112, 288)
Rien n'a été importé et le fichier a été supprimé.
Pour corriger :
1. Remplacez la colonne Email par un ID pseudo-anonyme stable — par exemple un
hachage SHA-256 de l'e-mail en minuscules, tronqué à 16 caractères.
Utilisez la MÊME méthode de hachage pour chaque fichier afin que la même personne
soit reconnaissable d'un webinaire à l'autre.
2. Supprimez entièrement la colonne Contact Number — elle n'est jamais utilisée.
3. Rédigez les adresses e-mail dans les réponses en texte libre, en conservant
le reste du commentaire.
Re-téléchargez une fois anonymisé. Voir references/anonymisation.md pour un script
prêt à l'emploi.
Pourquoi un ID pseudo-anonyme stable est négociable
La moitié de la valeur de cet outil réside dans l'analyse inter-webinaires : nouveaux vs participants de retour, cohortes de rétention, inscrits récurrents, taux de présence par personne. Tout cela nécessite de reconnaître la même personne à travers les fichiers sans savoir qui elle est. Dites-le clairement à l'utilisateur lors de l'accueil — s'ils anonymisent les e-mails en blancs ou en valeurs aléatoires par fichier, chaque graphique inter-webinaires devient silencieusement dénué de sens.
Recommandé : person_id = sha256(lower(strip(email)) + fixed_salt)[:16], avec le sel stocké en dehors de l'ensemble de données. references/anonymisation.md contient un script prêt à l'emploi.
Exigences de champs
Le tableau de bord fonctionne sur un schéma canonique. Les colonnes d'exportation de l'utilisateur sont mappées sur celui-ci. Tout ce que le mappage ne peut pas remplir est une fonctionnalité qui est masquée.
Entité canonique : webinar (une ligne par session)
| Champ | Obligatoire ? | Type | Débloque | Colonne source typique |
|---|---|---|---|---|
external_id | Obligatoire | texte, unique | Tout — la clé de jointure entre les fichiers participants/enquête/rediffusion | ID Webinaire, ID Réunion, ID Événement, ID Session |
topic | Obligatoire | texte | Tout étiquetage ; regroupement de campagne des rediffusions | Sujet, Titre, Nom de l'événement |
start_time | Obligatoire | horodatage | Chaque graphique temporel ; mois par mois ; heure de la journée ; la chronologie de concurrence | Heure de début réelle, Début, Date |
duration_minutes | Fortement recommandé | int | Axe x de la chronologie de concurrence ; % de session regardée | Durée réelle, Durée |
num_registrants | Recommandé | int | Entonnoir, taux de présence (peut être dérivé en comptant les lignes) | # Inscrits, Inscrit |
unique_viewers | Recommandé | int | Graphique de l'heure de la journée ; portée principale (dérivable) | Spectateurs uniques, Participants |
max_concurrent | Facultatif | int | Statistique de pic d'audience | Vues simultanées maximales |
Règle de fuseau horaire : décidez une fois si les horodatages stockés sont en UTC ou dans un fuseau local fixe, écrivez-le dans le commentaire du schéma, et ne convertissez jamais ad hoc. Le regroupement mensuel lit directement ce champ. Fournissez un mécanisme de remplacement manuel du mois (voir Remplacements de mois) pour les sessions qui se déroulent juste après minuit local pour un public dans un autre fuseau horaire.
Entité canonique : attendee_session (une ligne par personne par connexion)
Une personne qui se déconnecte et se reconnecte produit plusieurs lignes. Ne dédupliquez jamais à l'ingestion — dédupliquez à l'analyse, délibérément.
| Champ | Obligatoire ? | Type | Débloque | Colonne source typique |
|---|---|---|---|---|
webinar_id | Obligatoire | FK | Tout | — |
person_id | Obligatoire (pseudo-anonyme) | texte | Nouveaux vs retours, rétention, inscrits récurrents, déduplication par personne | Email haché, ID participant |
attended | Obligatoire | bool | Répartition présence/absence, entonnoir, taux de présence | Présent (Oui/Non) |
is_registrant | Recommandé | bool | Cohorte d'absents ; entonnoir | dérivé de la présence d'une heure d'inscription |
join_time | Recommandé | horodatage | Chronologie de concurrence, détection d'abandons | Heure de connexion |
leave_time | Recommandé | horodatage | Chronologie de concurrence, détection d'abandons | Heure de déconnexion |
minutes | Recommandé | int | Histogramme du temps de visionnage, temps de visionnage moyen | Temps passé (minutes) |
country / region | Facultatif | texte | Donut géographique, analyse temporelle régionale | Pays/Région |
organization | Facultatif | texte | Vue au niveau du compte | Organisation, Entreprise |
job_title | Facultatif | texte | Donut de séniorité (dérivé) | Titre de poste, Titre, Rôle |
source_name | Facultatif | texte | Donut du canal d'acquisition | Nom de la source, Source UTM, Référent |
registration_time | Facultatif | horodatage | Analyse de la courbe d'inscription | Heure d'inscription |
custom_answers | Facultatif | JSON | Un donut par question d'inscription | Toute colonne non reconnue |
Règle des colonnes non reconnues : toute colonne dans le fichier participant qui n'est pas l'un des champs connus ci-dessus est traitée comme une question d'enquête d'inscription et stockée dans custom_answers sous forme {question: answer}. Chacune de ces questions rend automatiquement son propre donut. C'est ce qui rend l'outil fonctionnel sur des plateformes que vous n'avez jamais vues.
Champ dérivé — seniority : regroupez job_title par mot-clé en C-Level / Fondateur (ceo, cto, cfo, cmo, coo, chief, founder, owner, president), VP / Directeur (vp, vice president, head of, director), Manager / Lead (manager, lead, principal), IC Senior (senior, sr.), Junior (junior, jr., intern, associate, assistant), sinon Contributeur Individuel. Masquez entièrement le graphique de séniorité lorsqu'aucune ligne n'a de titre de poste.
Entité canonique : feedback_response (une ligne par soumission d'enquête)
| Champ | Obligatoire ? | Type | Débloque | Colonne source typique |
|---|---|---|---|---|
webinar_id | Obligatoire | FK | Joindre les retours à la session | ID Réunion/Webinaire |
person_id | Facultatif (pseudo-anonyme) | texte | Lier les retours au comportement de présence | Adresse e-mail hachée |
submitted_at | Facultatif | horodatage | Ordre, récence | Date et heure de soumission |
score | Recommandé | int 1–5 | Distribution de la satisfaction + moyennes | « Dans quelle mesure avez-vous trouvé… » |
liked_text | Recommandé | texte | Regroupement de thèmes d'éloges | « Qu'avez-vous aimé… » |
improve_text | Recommandé | texte | Regroupement de thèmes de préoccupations | « Que pouvons-nous améliorer… » |
other_answers | Facultatif | JSON | Tout le reste, textuellement | colonnes de questions restantes |
Détection de colonne : trouvez les colonnes de score / like / amélioration par correspondance de mots-clés dans l'en-tête (useful/rate/score ; like ; improve), insensible à la casse, la première correspondance l'emporte. Montrez le mappage détecté à l'utilisateur et laissez-le le corriger — le libellé des questions d'enquête diffère d'une entreprise à l'autre.
Si l'enquête se trouve en dehors de la plateforme de webinaires (Typeform, Google Forms), elle ne portera pas d'ID de webinaire. Demandez alors à l'utilisateur soit d'ajouter un champ caché portant l'ID du webinaire, soit de sélectionner le webinaire cible dans l'interface utilisateur lors du téléchargement.
Entité canonique : replay_stats (une ligne par webinaire)
Les rediffusions sont les données les plus variables d'une plateforme à l'autre. Prenez en charge trois chemins d'ingestion, dans cet ordre de préférence :
- Connecteur API vers l'hôte de rediffusion (API YouTube Data/Analytics, Vimeo, Wistia) — uniquement si l'utilisateur en possède un et fournit des identifiants.
- Export CSV natif des analyses de l'hôte de rediffusion, mappé aux champs canoniques.
- Modèle CSV manuel — toujours disponible, toujours la solution de repli. Fournissez un modèle téléchargeable avec une ligne d'en-tête, un bloc de commentaires expliquant chaque métrique, et une ligne d'exemple.
| Champ | Obligatoire ? | Type | Débloque |
|---|---|---|---|
webinar_id | Obligatoire | clé de jointure | Tout ce qui concerne les rediffusions |
video_id / video_url | Facultatif | texte | Lien profond vers la vidéo hébergée ; synchronisation API |
views_day1 | Facultatif | int | Graphique de la demande précoce |
views_day7 | Facultatif | int | Graphique de la demande soutenue |
total_view_hours | Facultatif | numérique | Graphique de la consommation totale des rediffusions |
avg_view_duration_sec | Facultatif | int | Graphique de l'engagement des rediffusions |
avg_view_percentage | Facultatif | numérique | Graphique de rétention des rediffusions |
likes, comments, shares, subscribers_gained | Facultatif | int | Statistiques de réaction |
Chaque champ de rediffusion est facultatif. Stockez NULL pour les blancs, jamais 0 — une métrique non mesurée et une métrique mesurée
…
[tronqué pour la traduction]