Spec — Sistema de Ingestão de Eventos
Spec V1

Sistema de Ingestão de Eventos para Mapas Culturais

Hub de coleta, normalização e curadoria de eventos de plataformas de ingressos para integração com o Mapas Culturais de Fortaleza.

Problema

O Mapas Culturais de Fortaleza não possui alimentação automatizada de eventos externos. Atualmente, a cobertura de eventos é baixa, os dados são inconsistentes, há retrabalho manual na coleta e publicação, e risco de duplicidade. Eventos de plataformas de ingressos (Sympla, Bilheteria Virtual, Uhhuu, Eventim, Ticket For Fun) não são sistematicamente coletados e integrados ao Mapas.

Solução

Construir um hub de ingestão leve que:

  1. Coleta automática
    Eventos de plataformas de ingressos e fontes web
  2. Normalização e filtro
    Apenas eventos presenciais, em Fortaleza, nos próximos 30 dias
  3. Reconciliação
    Matching de espaços com o Mapas Culturais via API
  4. Curadoria
    Interface para revisão, edição e aprovação de eventos
  5. Exportação
    CSV para importação no Mapas
  6. Validação
    Conferência manual pós-importação
Prioridade
Entrega de valor com código mínimo: Node.js + SQLite, sem infraestrutura complexa, com painel administrativo pronto (AdminJS) e crawlers robustos (crawl4ai ou Puppeteer).

User Stories

Coleta e Ingestão

  1. As a system operator, I want daily automated crawling of ticket platforms, so that events are collected without manual intervention
  2. As a system operator, I want to crawl multiple platforms (Sympla, Bilheteria Virtual, Uhhuu, Eventim, Ticket For Fun), so that event coverage is comprehensive
  3. As a system operator, I want to supplement platform crawling with web search for events, so that I capture events from non-platform sources
  4. As a system operator, I want the system to filter only in-person events in Fortaleza within the next 30 days, so that irrelevant events are excluded
  5. As a system operator, I want the system to detect and handle duplicate events from the same platform (by external_id), so that the database stays clean
  6. As a system operator, I want crawling to run on a daily schedule, so that the system stays up-to-date automatically
  7. As a system operator, I want to see logs of each crawling run (success/failure, event count), so that I can monitor system health
  8. As a system operator, I want to receive email alerts when crawling fails, so that I can intervene quickly

Normalização e Dados

  1. As a data consumer, I want event titles, descriptions, and other text fields to be cleaned (HTML removed, spaces normalized), so that data is consistent
  2. As a data consumer, I want dates and times to be in a standardized format (ISO 8601, Fortaleza timezone), so that events can be compared and filtered reliably
  3. As a data consumer, I want venue name and address to be extracted and stored separately, so that matching with Mapas spaces is possible
  4. As a data consumer, I want to preserve the original raw payload from each platform, so that I can re-process if normalization logic changes
  5. As a data consumer, I want to store event price, avatar URL, and source URL, so that the exported CSV has all necessary fields

Matching e Reconciliação

  1. As a curator, I want the system to suggest Mapas spaces for each event based on venue name similarity, so that I don't have to manually search for matches
  2. As a curator, I want to see the top 3 candidate spaces when the match is uncertain (60-80% similarity), so that I can make an informed decision
  3. As a curator, I want to manually associate an event with a Mapas space when automatic matching fails, so that no event is left without a space
  4. As a curator, I want the system to sync Mapas spaces daily, so that the candidate list is always up-to-date
  5. As a curator, I want the system to mark events as "matched" (automatic), "suggested" (needs review), or "pending" (no match found), so that I can prioritize my work

Curadoria

  1. As a curator, I want to see a list of all collected events with filters (platform, status, date range), so that I can find events to review efficiently
  2. As a curator, I want to search events by title and description using full-text search, so that I can quickly find specific events
  3. As a curator, I want to edit event fields (title, description, venue, start time, end time, price), so that I can correct errors or improve data quality
  4. As a curator, I want to approve or reject events individually, so that only high-quality events are exported
  5. As a curator, I want to see the source URL of each event, so that I can verify details on the original platform
  6. As a curator, I want to see when an event was collected and last updated, so that I can assess data freshness
  7. As a curator, I want to log in with a simple email code (magic link), so that I don't need to remember passwords

Exportação e Validação

  1. As a curator, I want to generate a CSV file with all approved events that have a Mapas space assigned, so that I can import them into Mapas
  2. As a curator, I want the CSV to follow the exact format required by Mapas Culturais, so that import succeeds without errors
  3. As a curator, I want to download the generated CSV file, so that I can upload it to Mapas
  4. As a curator, I want to see a history of past exports (date, event count, file), so that I can track what was exported
  5. As a curator, I want to mark events as "imported" after successful import into Mapas, so that I can track the pipeline status
  6. As a curator, I want to mark events as "error" if import failed, so that I can investigate and re-export
  7. As a curator, I want to manually verify imported events via their Mapas URL, so that I can confirm they appear correctly

Dashboard e Monitoramento

  1. As a system operator, I want to see dashboard stats (total events collected, pending, approved, exported, imported), so that I can understand system throughput
  2. As a system operator, I want to see the status of each platform (last crawl time, success/failure), so that I can identify issues
  3. As a system operator, I want to see a timeline of sync runs (Mapas sync, platform crawls), so that I can verify the system is running as expected

Administração

  1. As a system operator, I want to add or deactivate platforms, so that I can control which sources are crawled
  2. As a system operator, I want to manually trigger a crawl for a specific platform, so that I can test changes or recover from failures
  3. As a system operator, I want to manually trigger a Mapas space sync, so that I can refresh the candidate list after changes in Mapas
  4. As a system operator, I want to view and delete events, so that I can clean up test data or invalid entries

Decisões de Implementação

Stack Tecnológica

Runtime
Node.js 20+ (TypeScript)
Framework Web
Fastify
ORM
Drizzle ORM
Banco
SQLite (better-sqlite3)
Busca
SQLite FTS5
Painel Admin
AdminJS
Auth
Magic Link (email)
Email
Nodemailer

Crawling

Opção preferida: crawl4ai (Python) — pronto, robusto, lida com JavaScript rendering

Alternativa Node.js: Puppeteer ou Playwright — mais código, mas integra melhor

Interface comum: Crawler interface com método fetchEvents(): Promise<RawEvent[]>

Rate limiting: delays entre requests, user-agent rotation

Retries: 3 tentativas com exponential backoff

Modelo de Dados

platforms: id, name, slug, is_active

events: id, platform_id, external_id, source_url, title, description, start_at, end_at, venue_name, venue_address, city, price, avatar_url, mapas_space_id, space_match_status, review_status, import_status, raw_json

venues: mapas_space_id, name, normalized_name, city, synced_at

sync_logs: id, type, platform_id, status, started_at, finished_at, details

exports: id, generated_at, total_items, file_path

export_items: id, export_id, event_id, status

FTS5 virtual tables: events_fts (title, description), venues_fts (name)

Matching de Espaço

Algoritmo: FTS5 para busca inicial + Levenshtein para similaridade

Thresholds:

  • Score > 80%: matched (automático)
  • Score 60-80%: suggested (mostrar top 3 candidatos)
  • Score < 60%: pending (curador decide)

Normalização: lowercase, remover acentos, remover palavras comuns (the, a, de, da)

Execução: matching em lote após crawling

Exportação CSV

Formato: CSV com campos: SEAL_ID, NOME, SUBTITULO, DESCRICAO_CURTA, LINGUAGEM, TAGS, PROJETO, PROPRIETARIO, ESPACO (mapas_space_id), HORA_INICIAL, HORA_FINAL, FREQUENCIA, DATA_INICIAL, DATA_FINAL, FAIXA_ETARIA, PRECO, AVATAR, LINKS (source_url)

Filtros: review_status = 'approved', mapas_space_id IS NOT NULL

Geração: biblioteca csv do Node.js

Armazenamento: arquivo em exports/ com timestamp no nome

Automação

Scheduler: node-cron (simples, in-process)

Jobs:

  • Sync Mapas spaces: diário 02:00
  • Crawl platforms: diário 03:00
  • Match venues: diário 04:00 (após crawl)

Logs: tabela sync_logs + console output

Alertas: email se status = 'failed'

Decisões de Teste

Princípios

  • Testar comportamento externo, não detalhes de implementação
  • Testar fluxos completos (end-to-end) para funcionalidades críticas
  • Testar casos de erro e edge cases
  • Usar fixtures para dados de exemplo

Módulos a Testar

  1. Crawlers
    Mockar respostas HTTP, validar parsing e normalização
  2. Normalizer
    Testar limpeza de texto, padronização de datas
  3. Matcher
    Testar similaridade com nomes conhecidos, validar thresholds
  4. CSV Exporter
    Testar formato do CSV, campos obrigatórios
  5. Mapas API client
    Mockar respostas, testar retries e error handling
  6. Auth
    Testar geração e validação de código magic link

Escopo

Fora do Escopo (V1)

  • RabbitMQ ou outro message broker
  • Meilisearch ou outro motor de busca externo
  • Docker/Kubernetes
  • Múltiplos usuários com permissões diferentes
  • Push via API do Mapas (só CSV)
  • Detecção de duplicidade entre plataformas diferentes
  • Machine learning para matching
  • Atualização automática de eventos já importados
  • Múltiplas cidades (só Fortaleza)
  • Eventos online ou híbridos (só presenciais)
  • Criação automática de espaços no Mapas
  • Webhooks ou notificações em tempo real
  • Dashboard com gráficos (só números)

Pode Entrar se Sobrar Tempo

  • Busca web complementar (eventos fora de plataformas conhecidas)
  • Dashboard com gráficos básicos
  • Exportação em outros formatos (JSON, XML)
  • Webhook para notificar curadores de novos eventos
  • Logs mais detalhados (audit trail de edições)

Notas Adicionais

Princípios de Design

  • Código mínimo, valor máximo: cada linha de código deve justificar sua existência
  • SQLite até provar que não aguenta: não adicionar complexidade de banco desnecessária
  • Automatizar só depois de manual funcionando: validar fluxo manualmente antes de automatizar
  • Curadoria humana é essencial: não tentar automatizar tudo, deixar decisões críticas para humanos

Riscos

Risco Mitigação
Scraping quebra (mudança de layout) Logs + alertas, manutenção pontual
API Mapas instável Retries, cache local (venues)
Matching impreciso Curadoria manual, ajuste de threshold
Volume de dados SQLite aguenta até ~100k eventos tranquilo

Métricas de Sucesso

  • Cobertura: >80% dos eventos das plataformas principais coletados
  • Qualidade: >90% dos eventos exportados importados com sucesso no Mapas
  • Velocidade: crawling completo em <30 minutos
  • Uso: curador consegue revisar 50 eventos/hora

Próximos Passos

Fase 1 — Fundação
3-5 dias
Setup Node.js + SQLite + schema, primeiro crawler (Sympla), normalizador básico
Fase 2 — Sync Mapas e Matching
3-5 dias
Cliente API Mapas, job de sync de espaços, matcher simples (FTS5 + Levenshtein)
Fase 3 — Painel de Curadoria
5-7 dias
Setup AdminJS, auth magic link, listagem, edição, aprovação de eventos
Fase 4 — Exportação e Validação
2-3 dias
Gerador de CSV, histórico de exportações, validação pós-importação
Fase 5 — Automação e Outras Plataformas
5-7 dias
Cron jobs, crawlers adicionais (Bilheteria Virtual, Uhhuu), logs, alertas
Fase 6 — Busca Web Complementar (opcional)
3-5 dias
crawl4ai para busca geral, parsing de resultados, integração com pipeline
Estimativa Total
Mínimo (V1 funcional): 18-22 dias
Com busca web: 23-27 dias