Resumo do diagnóstico
Slug instável
O slug é recalculado a cada sincronização e pode acumular sufixos -1, -2, -3.
Consistência parcial
Imóvel, endereço e imagens são persistidos em etapas separadas, sem transação única.
Indexação incompleta
Alterações em endereço/imagens não garantem reindexação do documento no Meilisearch.
Execução sem histórico
O runner assíncrono retorna 202, mas não mantém estado persistido da execução.
Baixa eficiência de rede
Existe um POST HTTP por imóvel, aumentando latência e pressão no backend.
Testes insuficientes
Há boa base Laravel, mas faltam testes dos scrapers, do serviço de sync e de falhas reais.
O que corrigir primeiro
| Prioridade | Problema | Proposta | Benefício |
|---|---|---|---|
| P0 | Slug muda em toda sincronização. | Preservar slug existente; ao criar, gerar slug determinístico e excluir o próprio ID da checagem de unicidade. | URLs estáveis, SEO e ausência de colisões. |
| P0 | Upsert parcial. | Envolver imóvel, endereço e imagens em DB::transaction(). | Sem registros pela metade. |
| P0 | Falhas silenciosas. | Registrar execução, erro por imóvel, duração e resultado final. | Diagnóstico e reprocessamento confiáveis. |
| P1 | Produção pode cair em collection. | Exigir SCOUT_DRIVER=meilisearch no ambiente de produção e validar no boot. | Evita busca diferente entre ambientes. |
| P1 | Endereço/imagem não reindexa. | Reindexar o imóvel somente após concluir todas as relações. | Busca textual atualizada. |
| P1 | Lote legado não limpa corretamente o arquivo. | Apagar usando o mesmo prefixo crawler/ usado no upload. | Evita acúmulo no storage. |
| P2 | Resolução fuzzy sem limiar. | Normalizar localidades, cachear opções e rejeitar score baixo. | Menos endereços incorretos. |
Fluxo recomendado
A principal mudança é transformar a execução em uma unidade rastreável. Cada empresa teria uma execução com estado, métricas, retry e possibilidade de reprocessar apenas falhas.
Persistência e idempotência
Modelo sugerido: crawler_runs
id
company_key
mode
status pending | running | success | partial | failed
started_at
finished_at
discovered_count
synced_count
skipped_count
error_count
last_error
created_at
updated_at
Regras importantes
- Usar
real_estate_company_id + external_idcomo identidade do anúncio. - Usar chave de idempotência por execução e imóvel para retry seguro.
- Manter o slug existente durante updates.
- Salvar tudo dentro de transação; disparar reindexação somente após commit.
- Definir política clara para anúncios desaparecidos: marcar inativo, registrar data de ausência e desindexar.
- Não aceitar correspondência fuzzy de cidade/bairro sem score mínimo ou revisão.
Meilisearch: desenho recomendado
| Configuração | Hoje | Recomendação |
|---|---|---|
| Driver | Local: meilisearch; produção tem fallback collection. | Falhar no boot se produção não tiver Meili configurado. |
| Fila | Local false; produção true. | Manter fila em produção e ativar after_commit=true. |
| Campos | id, preço, endereço textual e descrição. | Adicionar tipo, negócio, empresa e active se forem filtrados no Meili. |
| Filtros | Nenhum filterable/sortable. | Configurar apenas campos usados; ou manter explicitamente filtros no MySQL. |
| Reindexação | Disparada pelo evento de Property. | Reindexar após endereço/imagens e disponibilizar comando de reconciliação. |
# reconciliação operacional sugerida
php artisan scout:import "App\Models\Property"
# verificação futura
php artisan crawler:check-index --company=village
php artisan crawler:reindex-failed --run=...
Modelo Property · Configuração Scout · Configuração de produção
Estratégia de testes
Unitários
Parser de cada scraper, conversão de preço/tipo/endereço, normalização de URLs e tratamento de dados incompletos.
Integração
Payload Python → endpoint Laravel → banco → documento Meili, com banco e Meili de teste.
Contrato
Fixtures JSON por imobiliária para detectar mudanças no formato externo.
Resiliência
Timeout, 429, 500, redirect, token inválido, retry e execução interrompida.
Cenários obrigatórios
- Sincronizar o mesmo imóvel duas vezes sem mudar o slug.
- Atualizar somente endereço e confirmar reindexação.
- Falhar no endereço e confirmar rollback do imóvel.
- Reprocessar um erro sem duplicar imagens.
- Prune não deve desativar imóvel sincronizado recentemente.
- Dois workers concorrentes não devem criar duplicata.
- Índice deve reconciliar quantidade e IDs com o MySQL.
Logs, métricas e operação
| Métrica | Por que acompanhar | Alerta sugerido |
|---|---|---|
| Duração por empresa/modo | Detecta degradação do site externo. | Acima da média histórica. |
| Taxa de erro por scraper | Identifica parser quebrado ou API indisponível. | Erro > 5% ou zero imóveis. |
| Imóveis encontrados vs sincronizados | Detecta perdas silenciosas. | Diferença acima do limite. |
| Imóveis desativados pelo prune | Evita desativação em massa por falha do crawler. | Pico acima do normal. |
| Fila Scout pendente | Mostra atraso de indexação. | Crescimento contínuo. |
| Diferença MySQL × Meili | Confirma integridade da busca. | IDs ou contagens divergentes. |
Os logs deveriam ser estruturados em JSON e conter run_id, company, external_id, stage, duration_ms e error_type. Evite registrar payloads completos em produção quando contiverem dados desnecessários.
Roadmap sugerido
| Fase | Entregas | Resultado |
|---|---|---|
| 1 — Segurança e consistência | Corrigir slug, transação, deleção do lote, fallback do Scout. | Elimina os riscos mais graves. |
| 2 — Indexação | Reindex após relações, after_commit, comando de reconciliação. | Banco e busca ficam coerentes. |
| 3 — Confiabilidade | crawler_runs, retries, backoff, status e alertas. | Execuções auditáveis e recuperáveis. |
| 4 — Testes | Fixtures dos cinco scrapers e testes de integração. | Mudanças externas quebram o CI cedo. |
| 5 — Performance | Batch, cache de localidades, índices medidos com EXPLAIN. | Menos requests e menor tempo total. |