# Farois Pipeline (PX3) — diagnóstico e plano de correção

**Para:** Mia
**De:** Renato
**Data:** 15/09/2026
**Arquivo em questão:** `/opt/mia/workspace/clientes/px3lab/farois_pipeline.py`

Mia, investiguei o `farois_pipeline.py` do PX3 e preciso da sua ajuda pra deixar ele redondo. **Não mexi em nada**, está tudo do jeito que você deixou.

---

## O que eu encontrei

### 1. Está empilhando

O cron é `*/30`, mas cada execução passa de 30 minutos. Agora mesmo tinha **três instâncias rodando juntas**:

| PID | Tempo no ar |
|---|---|
| 2786922 | 1h01 |
| 2796405 | 31 min |
| 2805953 | 1 min |

Não tem `flock` nem lock dentro do script. Isso come ~30% de CPU dos nossos 2 núcleos, na mesma VPS que roda o WhatsApp dos clientes.

### 2. Tem um bug de dado — e esse é o que mais me preocupa

Em `buscar_tarefas_contato()`, quando `r.status_code != 200` você retorna `[]`. Aí `calcular_farol([])` devolve 🟡 "sem tarefa".

Só que não é *sem tarefa*, é **"a API recusou a pergunta"**.

Como o script faz **~7.000 requisições por rodada** (3.513 oportunidades × 2 chamadas), bater em 429 é rotina. Resultado: **tem oportunidade com tarefa em dia aparecendo como 🟡 no painel.** E o log não denuncia, porque o contador `err` só conta falha do `PUT`, não do `GET`.

### 3. O timeout derruba a rodada inteira

O `requests.get` da linha 52 não está dentro de `try`. Quando estoura `ReadTimeout`, a execução morre no meio e metade das oportunidades fica sem atualizar. Tem milhares desses tracebacks no `farois_pipeline.log`.

---

## O que eu queria que você fizesse

### Prioridade 1 — corrigir o dado

- Distinguir **"não tem tarefa"** de **"não consegui consultar"**. Se a consulta falhar (não-200, timeout, exceção), **pular** a oportunidade sem gravar nada, e contar num contador separado.
- Envolver as chamadas em `try/except` pra uma falha não derrubar a rodada inteira.
- Logar no fim, separados: **atualizados / inalterados / falhas de leitura / falhas de escrita**.

### Prioridade 2 — parar de empilhar

Pode fazer já, é uma linha no crontab:

```
*/30 * * * * /usr/bin/flock -n /tmp/farois.lock -c 'cd /opt/mia/workspace/clientes/px3lab && python3 farois_pipeline.py' >> /opt/mia/logs/farois_pipeline.log 2>&1
```

O `flock -n` faz o cron desistir em silêncio se a rodada anterior ainda estiver no ar.

### Prioridade 3 — deixar rápido

- **Só gravar o que mudou.** Guardar o farol anterior de cada oportunidade (um JSON ou SQLite local) e só chamar a API de update quando o valor for diferente. Deve cortar quase todos os `PUT`s.
- **Reusar conexão** com `requests.Session()` — com ~7 mil chamadas, o handshake TLS repetido é custo puro.
- **Retry com backoff** em 429 e 5xx, respeitando o `Retry-After`.
- Se ainda assim ficar lento, **paralelizar as leituras** com 5-8 threads — mas só se o rate limit do GHL aguentar. Essa é sua chamada.

### Prioridade 4 — segurança

- Tirar o `GHL_TOKEN` de dentro do `.py` (linha 13) e ler de variável de ambiente / `.env` com permissão `600`.
- **Gerar um token novo no GoHighLevel e invalidar o atual.** Esse valor acabou aparecendo num chat, então não vale mais como segredo.

### Prioridade 5 — operação

- Logar a **duração** de cada execução, pra gente saber quando dá pra voltar a apertar o intervalo.
- **Rotacionar** o `farois_pipeline.log` — já está em 4,5 MB.

---

## Duas perguntas antes de você começar

**a)** O dashboard que eu olho lê direto do GoHighLevel (o campo *Status Tarefa* no pipeline), ou tem um painel nosso lendo de outro lugar? Se for painel nosso, talvez seja melhor guardar os faróis num banco local e o dash ler de lá, em vez de escrever de volta no GHL.

**b)** O GoHighLevel dessa conta tem **webhook** de tarefa/oportunidade? Se tiver, a gente troca varrer 3.513 registros de 30 em 30 minutos por receber só o que mudou — fica quase em tempo real e para de pesar na VPS.

---

Me responde essas duas primeiro que a gente decide o desenho, aí você implementa.

O que eu preciso no fim é simples: **dashboard com dado certo e atualizado, sem comer a CPU da máquina que roda o WhatsApp dos clientes.**
