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:
-
Coleta automáticaEventos de plataformas de ingressos e fontes web
-
Normalização e filtroApenas eventos presenciais, em Fortaleza, nos próximos 30 dias
-
ReconciliaçãoMatching de espaços com o Mapas Culturais via API
-
CuradoriaInterface para revisão, edição e aprovação de eventos
-
ExportaçãoCSV para importação no Mapas
-
ValidaçãoConferência manual pós-importação
User Stories
Coleta e Ingestão
-
As a system operator, I want daily automated crawling of ticket platforms, so that events are collected without manual intervention
-
As a system operator, I want to crawl multiple platforms (Sympla, Bilheteria Virtual, Uhhuu, Eventim, Ticket For Fun), so that event coverage is comprehensive
-
As a system operator, I want to supplement platform crawling with web search for events, so that I capture events from non-platform sources
-
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
-
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
-
As a system operator, I want crawling to run on a daily schedule, so that the system stays up-to-date automatically
-
As a system operator, I want to see logs of each crawling run (success/failure, event count), so that I can monitor system health
-
As a system operator, I want to receive email alerts when crawling fails, so that I can intervene quickly
Normalização e Dados
-
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
-
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
-
As a data consumer, I want venue name and address to be extracted and stored separately, so that matching with Mapas spaces is possible
-
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
-
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
-
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
-
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
-
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
-
As a curator, I want the system to sync Mapas spaces daily, so that the candidate list is always up-to-date
-
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
-
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
-
As a curator, I want to search events by title and description using full-text search, so that I can quickly find specific events
-
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
-
As a curator, I want to approve or reject events individually, so that only high-quality events are exported
-
As a curator, I want to see the source URL of each event, so that I can verify details on the original platform
-
As a curator, I want to see when an event was collected and last updated, so that I can assess data freshness
-
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
-
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
-
As a curator, I want the CSV to follow the exact format required by Mapas Culturais, so that import succeeds without errors
-
As a curator, I want to download the generated CSV file, so that I can upload it to Mapas
-
As a curator, I want to see a history of past exports (date, event count, file), so that I can track what was exported
-
As a curator, I want to mark events as "imported" after successful import into Mapas, so that I can track the pipeline status
-
As a curator, I want to mark events as "error" if import failed, so that I can investigate and re-export
-
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
-
As a system operator, I want to see dashboard stats (total events collected, pending, approved, exported, imported), so that I can understand system throughput
-
As a system operator, I want to see the status of each platform (last crawl time, success/failure), so that I can identify issues
-
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
-
As a system operator, I want to add or deactivate platforms, so that I can control which sources are crawled
-
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
-
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
-
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
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
-
CrawlersMockar respostas HTTP, validar parsing e normalização
-
NormalizerTestar limpeza de texto, padronização de datas
-
MatcherTestar similaridade com nomes conhecidos, validar thresholds
-
CSV ExporterTestar formato do CSV, campos obrigatórios
-
Mapas API clientMockar respostas, testar retries e error handling
-
AuthTestar 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
Com busca web: 23-27 dias