🏷️

Normalização de Géneros

Normalização de Géneros e Categorias

Visão Geral

Sistema de normalização de géneros e categorias para garantir dados limpos e coerentes para IA/ML. Converte automaticamente tags e categorias diversas em géneros normalizados consistentes.

Motivação

Problema

Solução


Géneros Normalizados

📚 Tabela de Referência (38 Géneros)

Notícias e Informação

Música (11 géneros)

Desporto e Entretenimento

Cultura e Educação

Lifestyle

Outros


Mapeamentos

Exemplos de Variantes → Normalizado

Música Electrónica

EDM, Eletrónica, Eletronica, Dance, Techno, House, Trance
→ electronic

Hip Hop

Hip Hop, Hip-Hop, Rap, Trap, Underground
→ hip-hop

Notícias

News, Notícias, Information, Informação, Journalism, Atualidade
→ news

Desporto

Sports, Desporto, Futebol, Football, Soccer, F1, Ciclismo
→ sports

Mapeamento Completo

Ver ficheiro de referência: genre-mappings.ts


Arquitetura

Componentes

┌─────────────────────────────────────────────────┐
│         Ingestão de Conteúdos                   │
│  (Rádios, Podcasts, Episódios, Programas)      │
└──────────────────┬──────────────────────────────┘
                   │
                   ▼
┌─────────────────────────────────────────────────┐
│    ContentNormalizationService                  │
│                                                 │
│  • normalizeGenres(tags[])                     │
│  • normalizeLanguage(lang)                     │
│  • normalizeCountry(country)                   │
└──────────────────┬──────────────────────────────┘
                   │
                   ▼
┌─────────────────────────────────────────────────┐
│         Ficheiro de Referência                  │
│         genre-mappings.ts                       │
│                                                 │
│  • 38 géneros normalizados                     │
│  • ~300+ variantes mapeadas                    │
│  • Suporte PT, EN, ES                          │
└──────────────────┬──────────────────────────────┘
                   │
                   ▼
┌─────────────────────────────────────────────────┐
│         ContentFeatures                         │
│      (Dados Normalizados para ML)              │
│                                                 │
│  genres: NormalizedGenre[]                     │
│  language: string (ISO 639-1)                  │
│  country: string (ISO 3166-1)                  │
└─────────────────────────────────────────────────┘

Fluxo de Processamento

// 1. Conteúdo é ingerido com tags brutas
const podcast = {
  title: 'Tech Talks PT',
  categories: ['Tecnologia', 'Digital', 'Innovation']
};

// 2. Normalização automática
const genres = contentNormalizationService.normalizeGenres(
  podcast.categories
);
// Result: [NormalizedGenre.TECHNOLOGY]

// 3. Armazenamento em ContentFeatures
await contentFeaturesService.upsertPodcastFeatures({
  ...podcast,
  // genres é automaticamente normalizado
});

Uso

1. Todos os Conteúdos Passam pelo Normalizador

O serviço ContentFeaturesService automaticamente normaliza géneros em todas as operações:

// Rádio
await contentFeaturesService.upsertRadioFeatures({
  tags: ['Pop', 'Top 40', 'Hits'] 
  // → normalizado para [NormalizedGenre.MUSIC_POP]
});

// Podcast
await contentFeaturesService.upsertPodcastFeatures({
  categories: ['True Crime', 'Investigation']
  // → normalizado para [NormalizedGenre.TRUE_CRIME]
});

// Episódio herda géneros do podcast pai
await contentFeaturesService.upsertEpisodeFeatures({
  podcastId: podcast._id
  // → herda géneros normalizados do podcast
});

2. Normalização Manual (se necessário)

import { ContentNormalizationService } from '@app/common/services';

// Normalizar géneros
const genres = normalizationService.normalizeGenres([
  'EDM', 'Dance', 'Eletrónica'
]);
// Result: [NormalizedGenre.MUSIC_ELECTRONIC]

// Normalizar idioma
const language = normalizationService.normalizeLanguage('português');
// Result: 'pt'

// Normalizar país
const country = normalizationService.normalizeCountry('Portugal');
// Result: 'PT'

3. Consulta por Géneros Normalizados

// Buscar conteúdos por género normalizado
const electronicContent = await contentFeaturesModel.find({
  genres: NormalizedGenre.MUSIC_ELECTRONIC
});

// Buscar múltiplos géneros
const musicContent = await contentFeaturesModel.find({
  genres: {
    $in: [
      NormalizedGenre.MUSIC_POP,
      NormalizedGenre.MUSIC_ROCK,
      NormalizedGenre.MUSIC_ELECTRONIC
    ]
  }
});

Integração com Ingestão

População Inicial

# Executar migration para normalizar conteúdos existentes
npm run migrate:content-features

Este script:

  1. ✅ Processa todas as rádios ativas
  2. ✅ Processa todos os podcasts ativos
  3. ✅ Processa episódios (herdam géneros dos podcasts)
  4. ✅ Normaliza géneros, idiomas e países
  5. ✅ Cria/atualiza ContentFeatures

Integração Contínua

Os serviços de sincronização já integram normalização:

// sync-radios.processor.ts
for (const radio of newRadios) {
  await this.contentFeaturesService.upsertRadioFeatures({
    // ... dados da rádio
    // tags são automaticamente normalizadas
  });
}

// sync-podcasts.processor.ts
for (const podcast of updatedPodcasts) {
  await this.contentFeaturesService.upsertPodcastFeatures({
    // ... dados do podcast
    // categories são automaticamente normalizadas
  });
}

Adição de Novos Géneros

1. Adicionar ao Enum

// content-features.schema.ts
export enum NormalizedGenre {
  // ... existentes
  NEW_GENRE = 'new-genre',
}

2. Adicionar Mapeamentos

// genre-mappings.ts
export const GENRE_MAPPINGS: Record<string, NormalizedGenre> = {
  // ... existentes
  'variant 1': NormalizedGenre.NEW_GENRE,
  'variant 2': NormalizedGenre.NEW_GENRE,
  'variante pt': NormalizedGenre.NEW_GENRE,
};

3. Re-executar Migration

# Re-processar conteúdos existentes
npm run migrate:content-features

Validação e Testes

Verificar Normalização

// Testar normalização de uma tag
const genres = normalizationService.normalizeGenres(['EDM']);
console.log(genres); // [NormalizedGenre.MUSIC_ELECTRONIC]

// Verificar tags não mapeadas
const unknownGenres = normalizationService.normalizeGenres(['unknown-tag']);
console.log(unknownGenres); // [NormalizedGenre.OTHER]

Estatísticas de Géneros

# Ver distribuição de géneros
curl http://localhost:3333/api/admin/content-features/stats

Resposta:

{
  "byGenre": {
    "electronic": 150,
    "news": 89,
    "pop": 75,
    ...
  }
}

Boas Práticas

✅ Fazer

  1. Sempre usar géneros normalizados em queries e filtros
  2. Adicionar variantes ao ficheiro de mapeamentos quando necessário
  3. Re-executar migration após adicionar novos géneros
  4. Verificar estatísticas regularmente para detetar tags não mapeadas

❌ Evitar

  1. ❌ Usar tags brutas em queries
  2. ❌ Hardcode de strings em vez de enum
  3. ❌ Ignorar validação de géneros normalizados
  4. ❌ Criar géneros ad-hoc sem adicionar ao enum

Exemplos de Mapeamentos Comuns

Tags Originais Género Normalizado Nota
EDM, Dance, Eletrónica electronic Música electrónica
Pop, Top 40, Charts pop Música pop
Notícias, News, Journalism news Notícias
True Crime, Crime, Mystery true-crime True crime
Tech, Tecnologia, Digital technology Tecnologia
Sports, Futebol, Football sports Desporto
Podcast, Talk, Interview talk Conversas
Hip Hop, Rap, Trap hip-hop Hip hop

Monitorização

Métricas Importantes

  1. Taxa de normalização: % de tags que mapeiam com sucesso
  2. Tags não mapeadas: Quais tags caem em OTHER
  3. Distribuição de géneros: Identificar desbalanceamentos

Alertas

Configure alertas para:


Referências


Changelog

v1.0 - 24 Dezembro 2025