Taxonomia de Eventos - Audissey
Versão: 1.0
Data: 20 de dezembro de 2025
Status: Para Validação pelo PO
📋 Índice
- Visão Geral
- Campos Comuns (Global Properties)
- Eventos de Sessão
- Eventos de Reprodução
- Eventos de Interação
- Eventos de Descoberta
- Eventos de Navegação
- Eventos de Utilizador
- Resumo por Objetivo
- Implementação Técnica
🎯 Visão Geral
Este documento define todos os eventos que serão medidos na aplicação Audissey para:
- Produto: Métricas de engagement, retenção, funil de conversão
- ML: Alimentar modelo de recomendação personalizada
- Ambos: Eventos críticos para produto e ML
Princípios da Taxonomia
- Consistência: Nomes em snake_case, sempre em inglês
- Clareza: Nome do evento deve ser auto-explicativo
- Completude: Todos os campos necessários para análise
- Performance: Eventos não devem bloquear UX
- Privacy: Sem dados sensíveis nos eventos
🌐 Campos Comuns (Global Properties)
Estes campos devem ser enviados em todos os eventos:
| Campo | Tipo | Obrigatório | Descrição | Exemplo |
|---|---|---|---|---|
event_id |
string (UUID) | ✅ | Identificador único do evento | "550e8400-e29b-41d4-a716-446655440000" |
event_timestamp |
ISO 8601 | ✅ | Momento exato do evento (UTC) | "2025-12-20T14:30:00.000Z" |
user_id |
string (ObjectId) | ✅ | ID do utilizador (ou anonymous_id) | "507f1f77bcf86cd799439011" |
session_id |
string (UUID) | ✅ | ID da sessão atual | "a3b2c1d0-1234-5678-9abc-def012345678" |
device_type |
enum | ✅ | Tipo de dispositivo | "mobile", "tablet", "desktop", "web" |
device_os |
string | ✅ | Sistema operativo | "iOS 17.2", "Android 14", "macOS 14.0" |
app_version |
string | ✅ | Versão da app | "1.2.3" |
platform |
enum | ✅ | Plataforma | "ios", "android", "web" |
timezone |
string | ✅ | Timezone do utilizador | "Europe/Lisbon" |
hour_of_day |
integer (0-23) | ✅ | Hora local do utilizador | 14 |
day_of_week |
enum | ✅ | Dia da semana local | "monday", "tuesday", etc. |
network_type |
enum | ⚪ | Tipo de conexão | "wifi", "4g", "5g", "3g", "offline" |
referrer_screen |
string | ⚪ | Ecrã anterior | "home", "search", "favorites" |
🔐 Eventos de Sessão
`session_started`
Objetivo: Produto + ML
Quando: Utilizador abre a app ou inicia nova sessão web
Campos Específicos:
| Campo | Tipo | Obrigatório | Descrição | Exemplo |
|---|---|---|---|---|
is_first_session |
boolean | ✅ | É a primeira sessão do utilizador | true |
days_since_last_session |
integer | ⚪ | Dias desde última sessão (null se primeira) | 3 |
previous_session_duration_ms |
integer | ⚪ | Duração da sessão anterior (ms) | 180000 |
Payload Exemplo:
{
"event_name": "session_started",
"event_id": "uuid-123",
"event_timestamp": "2025-12-20T14:30:00.000Z",
"user_id": "507f1f77bcf86cd799439011",
"session_id": "session-uuid-456",
"device_type": "mobile",
"device_os": "iOS 17.2",
"app_version": "1.2.3",
"platform": "ios",
"timezone": "Europe/Lisbon",
"hour_of_day": 14,
"day_of_week": "friday",
"is_first_session": false,
"days_since_last_session": 2
}
`session_ended`
Objetivo: Produto + ML
Quando: Utilizador fecha a app ou sessão expira (30min inatividade)
Campos Específicos:
| Campo | Tipo | Obrigatório | Descrição | Exemplo |
|---|---|---|---|---|
session_duration_ms |
integer | ✅ | Duração total da sessão (ms) | 180000 |
content_played_count |
integer | ✅ | Número de conteúdos reproduzidos | 5 |
screens_viewed_count |
integer | ✅ | Número de ecrãs visualizados | 12 |
interactions_count |
integer | ✅ | Total de interações (plays, favorites, etc.) | 8 |
end_reason |
enum | ✅ | Motivo do término | "user_closed", "timeout", "crash" |
▶️ Eventos de Reprodução
`play_started`
Objetivo: Produto + ML (CRÍTICO)
Quando: Utilizador inicia reprodução de rádio, podcast ou episódio
Campos Específicos:
| Campo | Tipo | Obrigatório | Descrição | Exemplo |
|---|---|---|---|---|
content_id |
string (ObjectId) | ✅ | ID do conteúdo | "507f1f77bcf86cd799439011" |
content_type |
enum | ✅ | Tipo de conteúdo | "radio", "podcast_episode" |
content_title |
string | ✅ | Título do conteúdo | "Morning Show" |
content_language |
string | ⚪ | Idioma do conteúdo | "pt", "en" |
content_genres |
array[string] | ⚪ | Géneros (radios) ou categorias (podcasts) | ["news", "talk"] |
radio_id |
string (ObjectId) | ⚪ | ID da rádio (se type=radio) | "507f..." |
podcast_id |
string (ObjectId) | ⚪ | ID do podcast (se type=podcast_episode) | "507f..." |
program_id |
string (ObjectId) | ⚪ | ID do programa (se disponível) | "507f..." |
duration_seconds |
integer | ⚪ | Duração conhecida (null para rádio) | 1800 |
source |
enum | ✅ | Origem da reprodução | "recommendation", "search", "favorites", "category", "trending", "continue_listening", "radio_program" |
position_in_list |
integer | ⚪ | Posição na lista (se aplicável) | 3 |
playback_quality |
enum | ⚪ | Qualidade de áudio | "low", "medium", "high" |
Payload Exemplo:
{
"event_name": "play_started",
"event_id": "uuid-789",
"event_timestamp": "2025-12-20T14:35:22.000Z",
"user_id": "507f1f77bcf86cd799439011",
"session_id": "session-uuid-456",
"device_type": "mobile",
"hour_of_day": 14,
"day_of_week": "friday",
"content_id": "507f1f77bcf86cd799439012",
"content_type": "podcast_episode",
"content_title": "Como criar startups de sucesso",
"content_language": "pt",
"content_genres": ["business", "entrepreneurship"],
"podcast_id": "507f1f77bcf86cd799439013",
"duration_seconds": 2400,
"source": "recommendation",
"position_in_list": 2
}
`play_progress`
Objetivo: ML (CRÍTICO)
Quando: A cada 30 segundos durante reprodução
Campos Específicos:
| Campo | Tipo | Obrigatório | Descrição | Exemplo |
|---|---|---|---|---|
content_id |
string (ObjectId) | ✅ | ID do conteúdo | "507f..." |
content_type |
enum | ✅ | Tipo de conteúdo | "radio", "podcast_episode" |
playback_position_seconds |
integer | ✅ | Posição atual (segundos) | 120 |
duration_seconds |
integer | ⚪ | Duração total (null para rádio) | 2400 |
percent_completed |
float | ⚪ | Percentagem ouvida (null para rádio) | 0.05 |
buffer_count |
integer | ✅ | Número de bufferings | 1 |
is_background |
boolean | ✅ | Reprodução em background | false |
`play_paused`
Objetivo: Produto
Quando: Utilizador pausa reprodução
Campos Específicos:
| Campo | Tipo | Obrigatório | Descrição | Exemplo |
|---|---|---|---|---|
content_id |
string (ObjectId) | ✅ | ID do conteúdo | "507f..." |
content_type |
enum | ✅ | Tipo de conteúdo | "radio", "podcast_episode" |
playback_position_seconds |
integer | ✅ | Posição ao pausar | 150 |
duration_seconds |
integer | ⚪ | Duração total | 2400 |
pause_reason |
enum | ⚪ | Motivo da pausa | "user_action", "call_incoming", "notification", "error" |
`play_resumed`
Objetivo: Produto
Quando: Utilizador retoma reprodução após pausa
Campos Específicos:
| Campo | Tipo | Obrigatório | Descrição | Exemplo |
|---|---|---|---|---|
content_id |
string (ObjectId) | ✅ | ID do conteúdo | "507f..." |
content_type |
enum | ✅ | Tipo de conteúdo | "radio", "podcast_episode" |
playback_position_seconds |
integer | ✅ | Posição ao retomar | 150 |
pause_duration_seconds |
integer | ✅ | Tempo pausado | 30 |
`play_ended`
Objetivo: Produto + ML (CRÍTICO)
Quando: Reprodução termina (completada ou interrompida pelo utilizador)
Campos Específicos:
| Campo | Tipo | Obrigatório | Descrição | Exemplo |
|---|---|---|---|---|
content_id |
string (ObjectId) | ✅ | ID do conteúdo | "507f..." |
content_type |
enum | ✅ | Tipo de conteúdo | "radio", "podcast_episode" |
playback_duration_seconds |
integer | ✅ | Tempo total reproduzido | 180 |
content_duration_seconds |
integer | ⚪ | Duração total do conteúdo | 2400 |
percent_completed |
float | ⚪ | Percentagem ouvida | 0.075 |
completion_threshold_reached |
boolean | ✅ | Ouviu >80% (para podcasts) | false |
end_reason |
enum | ✅ | Motivo do término | "completed", "user_stopped", "user_skipped", "error", "next_content" |
buffer_count |
integer | ✅ | Total de bufferings | 2 |
average_bitrate_kbps |
integer | ⚪ | Bitrate médio | 128 |
`play_seek`
Objetivo: Produto
Quando: Utilizador salta para posição diferente (apenas podcasts)
Campos Específicos:
| Campo | Tipo | Obrigatório | Descrição | Exemplo |
|---|---|---|---|---|
content_id |
string (ObjectId) | ✅ | ID do conteúdo | "507f..." |
content_type |
enum | ✅ | Tipo (sempre podcast_episode) | "podcast_episode" |
from_position_seconds |
integer | ✅ | Posição anterior | 120 |
to_position_seconds |
integer | ✅ | Nova posição | 300 |
seek_direction |
enum | ✅ | Direção | "forward", "backward" |
💖 Eventos de Interação
`favorite_added`
Objetivo: Produto + ML (CRÍTICO)
Quando: Utilizador adiciona aos favoritos
Campos Específicos:
| Campo | Tipo | Obrigatório | Descrição | Exemplo |
|---|---|---|---|---|
content_id |
string (ObjectId) | ✅ | ID do conteúdo | "507f..." |
content_type |
enum | ✅ | Tipo | "radio", "podcast" |
content_title |
string | ✅ | Título | "RFM" |
source |
enum | ✅ | De onde adicionou | "player", "detail_page", "list" |
was_playing |
boolean | ✅ | Estava a reproduzir | true |
`favorite_removed`
Objetivo: Produto + ML
Quando: Utilizador remove dos favoritos
Campos Específicos:
| Campo | Tipo | Obrigatório | Descrição | Exemplo |
|---|---|---|---|---|
content_id |
string (ObjectId) | ✅ | ID do conteúdo | "507f..." |
content_type |
enum | ✅ | Tipo | "radio", "podcast" |
source |
enum | ✅ | De onde removeu | "player", "favorites_list" |
days_as_favorite |
integer | ✅ | Dias como favorito | 15 |
`content_shared`
Objetivo: Produto
Quando: Utilizador partilha conteúdo
Campos Específicos:
| Campo | Tipo | Obrigatório | Descrição | Exemplo |
|---|---|---|---|---|
content_id |
string (ObjectId) | ✅ | ID do conteúdo | "507f..." |
content_type |
enum | ✅ | Tipo | "radio", "podcast", "podcast_episode" |
share_method |
enum | ✅ | Método | "whatsapp", "twitter", "facebook", "instagram", "copy_link", "email" |
source |
enum | ✅ | De onde partilhou | "player", "detail_page" |
`search_performed`
Objetivo: Produto + ML
Quando: Utilizador faz pesquisa
Campos Específicos:
| Campo | Tipo | Obrigatório | Descrição | Exemplo |
|---|---|---|---|---|
search_query |
string | ✅ | Texto pesquisado | "podcasts tecnologia" |
search_filters |
object | ⚪ | Filtros aplicados | {"type": "podcast", "language": "pt"} |
results_count |
integer | ✅ | Número de resultados | 23 |
search_duration_ms |
integer | ✅ | Tempo de resposta | 150 |
`search_result_clicked`
Objetivo: Produto + ML (CRÍTICO)
Quando: Utilizador clica num resultado de pesquisa
Campos Específicos:
| Campo | Tipo | Obrigatório | Descrição | Exemplo |
|---|---|---|---|---|
search_query |
string | ✅ | Texto pesquisado | "podcasts tecnologia" |
content_id |
string (ObjectId) | ✅ | ID do conteúdo clicado | "507f..." |
content_type |
enum | ✅ | Tipo | "radio", "podcast", "podcast_episode" |
position_in_results |
integer | ✅ | Posição na lista (1-indexed) | 3 |
total_results |
integer | ✅ | Total de resultados | 23 |
`rating_submitted`
Objetivo: Produto + ML
Quando: Utilizador avalia conteúdo (se implementado)
Campos Específicos:
| Campo | Tipo | Obrigatório | Descrição | Exemplo |
|---|---|---|---|---|
content_id |
string (ObjectId) | ✅ | ID do conteúdo | "507f..." |
content_type |
enum | ✅ | Tipo | "radio", "podcast", "podcast_episode" |
rating_value |
integer (1-5) | ✅ | Avaliação | 4 |
previous_rating |
integer | ⚪ | Avaliação anterior (null se primeira) | 3 |
🔍 Eventos de Descoberta
`content_impression`
Objetivo: ML (CRÍTICO)
Quando: Conteúdo é exibido no ecrã (viewport) por >1 segundo
Campos Específicos:
| Campo | Tipo | Obrigatório | Descrição | Exemplo |
|---|---|---|---|---|
content_id |
string (ObjectId) | ✅ | ID do conteúdo | "507f..." |
content_type |
enum | ✅ | Tipo | "radio", "podcast", "podcast_episode" |
content_title |
string | ✅ | Título | "Tech News Daily" |
list_type |
enum | ✅ | Tipo de lista | "recommendations", "trending", "search", "category", "favorites", "continue_listening" |
position_in_list |
integer | ✅ | Posição (1-indexed) | 5 |
impression_duration_ms |
integer | ✅ | Tempo visível | 2500 |
Nota: Crítico para negative implicit feedback (viu mas não clicou)
`content_viewed`
Objetivo: Produto
Quando: Utilizador abre página de detalhes do conteúdo
Campos Específicos:
| Campo | Tipo | Obrigatório | Descrição | Exemplo |
|---|---|---|---|---|
content_id |
string (ObjectId) | ✅ | ID do conteúdo | "507f..." |
content_type |
enum | ✅ | Tipo | "radio", "podcast" |
source |
enum | ✅ | Origem | "recommendation", "search", "favorites", "category" |
view_duration_ms |
integer | ⚪ | Tempo na página (enviado ao sair) | 5000 |
`list_scrolled`
Objetivo: Produto
Quando: Utilizador faz scroll numa lista
Campos Específicos:
| Campo | Tipo | Obrigatório | Descrição | Exemplo |
|---|---|---|---|---|
list_type |
enum | ✅ | Tipo de lista | "recommendations", "trending", "search", "category" |
scroll_depth |
float (0-1) | ✅ | Profundidade do scroll | 0.75 |
items_viewed |
integer | ✅ | Itens visualizados | 12 |
total_items |
integer | ✅ | Total de itens | 50 |
🧭 Eventos de Navegação
`screen_viewed`
Objetivo: Produto
Quando: Utilizador acede a um ecrã/página
Campos Específicos:
| Campo | Tipo | Obrigatório | Descrição | Exemplo |
|---|---|---|---|---|
screen_name |
enum | ✅ | Nome do ecrã | "home", "search", "favorites", "player", "profile", "radio_detail", "podcast_detail", "episode_detail", "category" |
screen_category |
enum | ⚪ | Categoria do ecrã | "discovery", "consumption", "profile", "settings" |
previous_screen |
string | ⚪ | Ecrã anterior | "home" |
`tab_changed`
Objetivo: Produto
Quando: Utilizador muda de tab na navegação principal
Campos Específicos:
| Campo | Tipo | Obrigatório | Descrição | Exemplo |
|---|---|---|---|---|
from_tab |
enum | ✅ | Tab anterior | "home", "radios", "podcasts", "favorites", "profile" |
to_tab |
enum | ✅ | Nova tab | "radios" |
👤 Eventos de Utilizador
`user_registered`
Objetivo: Produto
Quando: Utilizador completa registo
Campos Específicos:
| Campo | Tipo | Obrigatório | Descrição | Exemplo |
|---|---|---|---|---|
registration_method |
enum | ✅ | Método de registo | "email", "google", "apple", "facebook" |
has_profile_name |
boolean | ✅ | Preencheu nome | true |
has_preferred_language |
boolean | ✅ | Definiu idioma | false |
`user_logged_in`
Objetivo: Produto
Quando: Utilizador faz login
Campos Específicos:
| Campo | Tipo | Obrigatório | Descrição | Exemplo |
|---|---|---|---|---|
login_method |
enum | ✅ | Método de login | "email", "google", "apple", "facebook" |
days_since_last_login |
integer | ⚪ | Dias desde último login | 5 |
`profile_updated`
Objetivo: Produto
Quando: Utilizador atualiza perfil
Campos Específicos:
| Campo | Tipo | Obrigatório | Descrição | Exemplo |
|---|---|---|---|---|
fields_updated |
array[string] | ✅ | Campos alterados | ["name", "preferredLanguage"] |
`user_logged_out`
Objetivo: Produto
Quando: Utilizador faz logout
Campos Específicos: Apenas campos comuns
📊 Resumo por Objetivo
Eventos CRÍTICOS para ML (Modelo de Recomendação)
- ✅
play_started- Implicit positive feedback - ✅
play_progress- Engagement depth - ✅
play_ended- Completion rate - ✅
favorite_added- Explicit positive feedback - ✅
content_impression- Negative implicit feedback (viu mas não interagiu) - ✅
search_result_clicked- Intent signal
Eventos para Produto (Métricas & Analytics)
- Engagement:
session_started,session_ended,screen_viewed,tab_changed - Retenção:
user_logged_in, dias entre sessões - Qualidade:
buffer_count,play_seek,errorevents - Viralidade:
content_shared - Conversão:
user_registered,profile_updated
Eventos para Ambos (Produto + ML)
play_started- Padrões de consumo + training dataplay_ended- Métricas de completion + quality signalfavorite_added/removed- Engagement + explicit feedbacksearch_performed- UX + user preferences
🔧 Implementação Técnica
Priorização de Eventos (MVP)
Fase 1 - Essenciais (Semana 1-2)
✅ session_started
✅ session_ended
✅ play_started
✅ play_progress (30s)
✅ play_ended
✅ favorite_added
✅ favorite_removed
Fase 2 - Importantes (Semana 3-4)
✅ content_impression
✅ search_performed
✅ search_result_clicked
✅ screen_viewed
✅ user_registered
✅ user_logged_in
Fase 3 - Nice-to-Have (Após MVP)
⚪ play_paused
⚪ play_resumed
⚪ play_seek
⚪ content_shared
⚪ rating_submitted
⚪ list_scrolled
Stack de Analytics Sugerida
Opção 1: Open Source
- Coleta: Segment (CDP) ou RudderStack
- Storage: BigQuery ou PostgreSQL + TimescaleDB
- Processing: Apache Spark ou dbt
- ML: Python (scikit-learn, TensorFlow, PyTorch)
Opção 2: Managed
- Coleta: Amplitude ou Mixpanel
- ML: AWS SageMaker ou Google Vertex AI
- Custo: ~$200-500/mês (até 100k users)
Opção 3: Hybrid (Recomendado)
- Eventos de Produto: Mixpanel/Amplitude (análise rápida)
- Eventos de ML: Pipeline próprio → BigQuery → Python
- Custo: ~$100-300/mês
Estrutura de Envio
// SDK de Analytics (exemplo)
interface AnalyticsEvent {
event_name: string;
event_id: string;
event_timestamp: string;
user_id: string;
session_id: string;
// ... campos comuns
properties: Record<string, any>; // Campos específicos do evento
}
// Exemplo de uso
analytics.track('play_started', {
content_id: '507f1f77bcf86cd799439011',
content_type: 'podcast_episode',
content_title: 'Como criar startups',
source: 'recommendation',
position_in_list: 2
});
Validação de Eventos
// Schema validation com Zod
import { z } from 'zod';
const PlayStartedSchema = z.object({
event_name: z.literal('play_started'),
content_id: z.string().regex(/^[a-f\d]{24}$/i),
content_type: z.enum(['radio', 'podcast_episode']),
content_title: z.string().min(1),
source: z.enum([
'recommendation',
'search',
'favorites',
'category',
'trending',
'continue_listening',
'radio_program'
]),
position_in_list: z.number().int().positive().optional(),
// ... outros campos
});
Sampling Strategy
Para apps com alto volume (>1M eventos/dia):
- session_started/ended: 100% (sempre)
- play_started/ended: 100% (sempre)
- play_progress: 100% para primeiros 5min, depois 50%
- content_impression: 30% (sampling aleatório)
- screen_viewed: 50%
Performance Guidelines
Eventos não devem bloquear UI
- Envio assíncrono
- Queue local com retry
- Batch de eventos (máx 50 por request)
Payloads leves
- Máximo 10KB por evento
- Evitar arrays grandes
- Comprimir se >5KB
Rate limiting
- Máximo 10 eventos/segundo por utilizador
- Deduplicação por event_id
Privacy & GDPR
Dados Sensíveis - NUNCA enviar:
- Passwords
- Emails completos (usar hash)
- IP addresses completos
- Números de telefone
Consent:
- Analytics essenciais: Não requer consent
- ML/Personalização: Requer opt-in
- Marketing: Requer opt-in explícito
Data Retention:
- Eventos de produto: 2 anos
- Eventos de ML: 1 ano
- Agregados: Indefinido
Right to be Forgotten:
- Anonimizar user_id após 30 dias do pedido
- Manter agregados sem PII
✅ Checklist de Validação
Para o PO/Founder validar:
- Eventos de Produto: Cobre todas as métricas necessárias para KPIs?
- Eventos de ML: Dados suficientes para treinar modelo de recomendação?
- Nomes de Eventos: Claros e consistentes?
- Campos Obrigatórios: Todos fazem sentido?
- Privacy: Em compliance com GDPR?
- Priorização: Fases 1, 2, 3 fazem sentido para roadmap?
- Stack Técnica: Opção escolhida alinha com orçamento?
- Performance: Guidelines não vão impactar UX?
Perguntas para Discussão:
- Rádios em direto: Como medir engagement se não há "duration"? (Resposta atual:
play_progressa cada 30s) - Programs: Queremos trackear qual programa estava a ser emitido quando user ouviu? (Resposta: Sim, via
program_idemplay_started) - Offline playback: Como trackear? (Sugestão: Queue de eventos, enviar quando voltar online)
- Notifications: Trackear push notifications? (Sugestão: Fase 3)
- Sleep timer: Implementar evento
sleep_timer_set? (Sugestão: Fase 3)
📝 Changelog
| Data | Versão | Alteração |
|---|---|---|
| 2025-12-20 | 1.0 | Versão inicial para validação |
👥 Aprovações
- PO/Founder - Tiago
- Tech Lead -
- Data Team -
- Privacy/Legal -
Próximos Passos:
- ✅ Validação deste documento pelo PO
- ⏳ Implementação SDK de analytics (Fase 1)
- ⏳ Setup de infrastructure (BigQuery/Mixpanel)
- ⏳ Integração nos clients (iOS/Android/Web)
- ⏳ Dashboard de validação (verificar eventos a chegar)