📅

Radio Schedules & EPG

Radio Programs & EPG Implementation

Visão Geral

Sistema completo de gestão de programação de rádios (EPG - Electronic Program Guide) usando a API do MyTuner Radio. Permite obter, armazenar e consultar a grade de programação de rádios portuguesas para os próximos 7 dias.

Arquitetura

Componentes Principais

  1. MyTunerRadioService - Integração com API MyTuner Radio
  2. RadioSchedule Schema - Modelo de dados para armazenamento EPG
  3. Scripts de Coleta - Automação de coleta de EPG
  4. Scripts de Validação - Verificação de qualidade dos dados

MyTuner Radio API

Processo de Autenticação (3 Passos)

A API do MyTuner requer um processo de 3 passos para obter EPG:

// 1. Pesquisar rádio e obter ID
const searchResult = await searchRadio("Antena 1");
// Retorna: { radioId, matchedName, distance }

// 2. Registar widget e obter access token
const accessToken = await registerWidget(searchResult.radioId);

// 3. Obter programação de 7 dias
const schedule = await getStationSchedule(accessToken, searchResult.radioId);

Algoritmo de Matching (Levenshtein Distance)

Para lidar com variações de nomes de rádios, o sistema utiliza:

  1. Limpeza de Nomes:

    • Remove sufixos: "(Portugal)", "(AAC)", "(Online)"
    • Remove a palavra "rádio" / "Radio"
    • Normaliza espaços
  2. Cálculo de Similaridade:

    • Algoritmo de Levenshtein Distance
    • Retorna distância numérica (0 = match exato)
    • Threshold configurável (padrão: 15)
  3. Validação:

    if (levenshteinDistance > THRESHOLD) {
      // Match considerado inválido
      // Rádio provavelmente não existe no MyTuner
    }
    

Exemplo de Matching

Nome Original: "Rádio Comercial (Portugal)"
Nome Limpo: "Comercial"
Resultados MyTuner: ["Rádio Comercial", "Comercial 90.8", ...]
Melhor Match: "Rádio Comercial" (distância: 6)
✅ VÁLIDO (< threshold de 15)
Nome Original: "Rádio Elvas"  
Nome Limpo: "Elvas"
Resultados MyTuner: ["Antena 1", "RFM", "Mega Hits", ...]
Melhor Match: "Antena 1" (distância: 17)
❌ INVÁLIDO (> threshold de 15)

Schema de Dados

RadioSchedule

{
  radio_id: ObjectId,           // Referência à rádio
  radio_name: string,            // Nome da rádio
  mytuner_id: string,            // ID no MyTuner
  schedule: ProgramEntry[][],    // 7 dias de programação
  fetched_at: Date,              // Data de coleta
  expires_at: Date,              // Data de expiração (7 dias)
  source: 'mytuner'              // Fonte dos dados
}

ProgramEntry

{
  time: string,          // "07:00"
  end_time: string,      // "10:00"
  title: string,         // Nome do programa
  subtitle: string,      // Apresentador/Descrição
  description: string,   // Descrição adicional
  duration: number,      // Duração em minutos
  
  // Dias da semana
  days: boolean[],       // [mon, tue, wed, thu, fri, sat, sun]
  monday: boolean,
  tuesday: boolean,
  wednesday: boolean,
  thursday: boolean,
  friday: boolean,
  saturday: boolean,
  sunday: boolean
}

Scripts Disponíveis

1. collect-epg.ts

Propósito: Coletar EPG para todas as rádios portuguesas

Características:

Configurações:

const CONFIG = {
  LEVENSHTEIN_THRESHOLD: 15,     // Distância máxima aceitável
  REQUEST_DELAY_MS: 2000,        // Delay entre requests
  SCHEDULE_EXPIRATION_DAYS: 7,   // Dias até expiração
  FORCE_REFRESH: false,          // Forçar atualização
};

Execução:

npm run collect:epg

Output:

🔍 Iniciando coleta de EPG para rádios portuguesas
⚙️  Configuração:
   - Threshold Levenshtein: 15
   - Delay entre requests: 2000ms
   - Forçar atualização: NÃO

📻 Total de rádios portuguesas: 230
📋 Rádios a processar: 190
✅ Rádios com EPG válido: 40

[1/190] 📻 Antena 1
   ✅ EPG salvo com sucesso!
   🎯 Match: "Antena 1" → "Antena 1 (Portugal)"
   📏 Distância: 11
   📊 MyTuner ID: 396103
   📊 Programas: 227
   ⏱️  Expira em: 31/12/2025

...

============================================================
📊 ESTATÍSTICAS FINAIS
============================================================
Total de rádios processadas: 190
✅ Sucesso: 35 (18.4%)
⚠️  Não encontradas no MyTuner: 143 (75.3%)
❌ Matches inválidos (threshold): 10 (5.3%)
🔄 Já tinham EPG válido: 40
❌ Erros: 2
============================================================

✨ Total de rádios com EPG: 75/230 (32.6%)

2. validate-schedules.ts

Propósito: Validar qualidade dos dados armazenados

Verifica:

  1. Entradas duplicadas por radio_id
  2. Schedules partilhados entre rádios diferentes
  3. Estatísticas gerais

Execução:

npm run validate:schedules

Output:

📊 Total de schedules: 40
✅ Sem duplicados por radio_id

⚠️  Encontrados 7 schedules partilhados:
   Schedule partilhado por 9 rádios:
     - Antena 1
     - Antena 1 (Portugal)
     - Antena 1 (AAC)
     ...

📊 Estatísticas:
   Total de schedules: 40
   Rádios únicas: 40
   Duplicados: 0
   Schedules partilhados: 7
   Total de programas: 4859
   Média de programas por rádio: 121.5

3. reprocess-local-radios.ts

Propósito: Reprocessar rádios específicas (para debugging)

Execução:

npm run reprocess:local-radios

4. clean-local-schedules.ts

Propósito: Limpar schedules incorretos

Execução:

npm run clean:local-schedules

Problemas Conhecidos & Limitações

1. Rádios Locais sem EPG

Problema: Muitas rádios locais portuguesas não têm EPG no MyTuner

Comportamento:

Solução: Sistema marca como "não encontrada" e continua

2. Rádios com Variantes

Problema: Mesma rádio aparece múltiplas vezes na BD com nomes diferentes

Exemplos:

Impacto: Schedules duplicados mas legítimos

Solução Futura: Normalização de nomes ou merge de rádios duplicadas

3. Retransmissoras

Problema: Rádios locais que são retransmissoras de grandes rádios

Exemplos:

Comportamento: Sistema atribui corretamente o schedule da rádio principal

Nota: Isto é tecnicamente correto, mas pode ser confuso

4. Rate Limiting

Problema: MyTuner pode bloquear requests excessivos

Solução:

5. Cookies Expirados

Problema: Cookies do browser têm validade limitada

Sintoma: Erro 403 ou 401 no MyTuner

Solução:

  1. Abrir https://mytuner-radio.com em browser
  2. Copiar novos cookies do DevTools
  3. Atualizar sessionCookies no serviço

Melhorias Futuras

Alta Prioridade

  1. Automatic Cookie Refresh

    • Renovar cookies automaticamente
    • Evitar necessidade de atualização manual
  2. EPG from Alternative Sources

    • RadioBrowser API
    • Websites das próprias rádios
    • Scraping quando MyTuner não tem dados
  3. Radio Deduplication

    • Base implementada na API: campos derivados para agrupamento rápido
    • Backfill via endpoint admin para preencher docs antigos
    • Fusão/merge final ainda depende de regras de negócio (não automático)

Média Prioridade

  1. Scheduled EPG Collection

    • Cron job diário para atualizar EPG expirado
    • Notificações de falhas
  2. EPG Quality Metrics

    • Dashboard de cobertura
    • Alertas para rádios sem EPG
  3. Manual EPG Entry

    • Interface para adicionar programação manualmente
    • Para rádios sem EPG automático

Baixa Prioridade

  1. EPG Prediction

    • ML para prever programação baseado em histórico
    • Para rádios com programação regular
  2. Multi-Language Support

    • EPG para rádios de outros países
    • Suporte a diferentes fusos horários

API de Consulta

Deduplicação (Base)

Para suportar deduplicação sem impactar performance de search, a API pré-computa
campos derivados no Radio (por exemplo: name_normalized, name_tokens,
brand_tokens, variant_tokens, stream_domain, homepage_domain,
dedupe_fingerprint).

Backfill (Admin)

Como esses campos são computados em pre('save'), documentos antigos podem não
ter esses valores. O admin portal pode disparar o backfill via:

O backfill cria entradas em job_execution_logs com jobType=backfill-radios-dedupe-fields
para visibilidade na página de ingestion status/history.

Obter EPG de uma Rádio

// No controller/service
const schedule = await radioScheduleModel.findOne({
  radio_id: radioId,
  expires_at: { $gt: new Date() } // Apenas válidos
});

if (!schedule) {
  // Rádio não tem EPG ou expirou
  return null;
}

// Retornar schedule.schedule (7 dias de programação)

Obter Programa Atual

const now = new Date();
const currentDay = now.getDay(); // 0 = Sunday
const currentTime = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`;

// Encontrar programa no dia atual
const todayPrograms = schedule.schedule[currentDay];
const currentProgram = todayPrograms.find(program => {
  return program.time <= currentTime && 
         (!program.end_time || program.end_time > currentTime);
});

Manutenção

Atualizar EPG Manualmente

# Forçar atualização de todas as rádios
# Modificar FORCE_REFRESH = true em collect-epg.ts
npm run collect:epg

Limpar EPG Expirado

// Via MongoDB
db.radioschedules.deleteMany({
  expires_at: { $lt: new Date() }
});

Verificar Qualidade

# Executar validação
npm run validate:schedules

# Verificar logs para problemas

Troubleshooting

"Failed to search radio"

Causa: Cookies inválidos ou rate limiting

Solução:

  1. Verificar cookies no serviço
  2. Aumentar REQUEST_DELAY_MS
  3. Verificar conectividade com MyTuner

"Invalid radio" para rádio conhecida

Causa: Rádio não tem EPG no MyTuner (comportamento normal)

Solução: Isto é esperado para ~75% das rádios locais

Match incorreto (distância alta)

Causa: Rádio não existe no MyTuner

Solução:

  1. Sistema já rejeita automaticamente (threshold)
  2. Verificar se rádio realmente existe no MyTuner
  3. Ajustar nome da rádio na BD se necessário

Schedules partilhados suspeitos

Causa: Pode ser legítimo (retransmissoras) ou erro de matching

Solução:

  1. Executar validate:schedules
  2. Verificar distância Levenshtein nos logs
  3. Se inválido, executar clean:local-schedules

Recursos

Contato

Para questões ou problemas, consultar a documentação técnica ou abrir issue no repositório.