hub
F5 Hub
Painel de Sistemas
flash_on

Viral Detector

Inteligência de Conteúdo
● Online

Monitora 92 perfis do Instagram e detecta posts viralizando

info O que faz

Scraping público via Apify a cada dia 04:00. Calcula viral_score (engagement ÷ mediana do perfil) e is_benchmark (outlier relativo + top 10% global). Expõe API pra Foxy. Detecta virais em tempo real e envia alerta no WhatsApp.

schedule Agendamentos (7)

F1 — Discovery ClickUp 0 8 * * *
Diário 08:00 BRT
Lê tasks novas do ClickUp (status=roteiro), enfileira URLs
F1b — URL Resolver 5 8 * * *
Diário 08:05 BRT
Descobre owner de URLs de posts via Apify, cadastra novos perfis
F4b — Daily Top 20 Ativos 0 4 * * 1-6
Seg-Sáb 04:00 BRT
Coleta novos posts dos 20 perfis mais prolíficos (posts/semana)
F4 — Weekly Update 0 4 * * 0
Domingo 04:00 BRT
Coleta de TODOS os 92 perfis. Inativa perfis 60+ dias sem coleta
Viral Alerter (WhatsApp) 30 4 * * *
Diário 04:30 BRT
Detecta virais (velocity P90) → Whisper → Tier → abordagem → WhatsApp
Job Processor */30 * * * * (interval)
A cada 30s
Consome collection_jobs queued (scrape/calculate/recompute)
Coleta noturna URLs pendentes (cron) 0 0 * * *
Diário 00:00 BRT
Coleta_direct das URLs em coleta_pendente.json via Apify

hub Conexões

description Documentação (CLAUDE.md)

# CLAUDE.md — Contexto Permanente do Projeto
# F5 Têxtil | Viral Detector (Instagram Public Scraper)
# Atualizado em: 2026-04-11 (Fase 0 — scaffolding documental)

## 1. SOBRE O PROJETO

**Nome:** Viral Detector — Instagram Public Scraper
**Objetivo:** Monitorar automaticamente perfis públicos do Instagram descobertos nos roteiros do ClickUp, detectar quais posts viralizaram comparando com a mediana histórica por tipo de post do próprio perfil, e entregar esses dados ao Foxy para análise de padrões de conteúdo viral.
**Fase atual:** MVP — Fase 0 (scaffolding documental completa, código ainda não escrito)
**Documentação completa:** [PRD.md](PRD.md) · [ARCHITECTURE.md](ARCHITECTURE.md) · [SCHEMA.md](SCHEMA.md) · [STATUS.md](STATUS.md)
**Plano de execução em fases:** [.claude/plan.md](.claude/plan.md) — 14 fases (0 → 13)
**Projeto pai:** Foxy Intelligence Layer
**Complementar a:** `/root/instagram-collector` (módulo OAuth autenticado — dados privados) · `/root/clickup-agent/instagram_analyzer.py` (análise 1-post-por-task via scraping cookies — classifica e descarta)

## 2. STACK TECNOLÓGICO

- **Linguagem:** Python 3.12 (`python:3.12-slim` no Docker)
- **Framework:** FastAPI 0.115.0 + Uvicorn 0.30.6
- **Scheduler:** APScheduler 3.11.0 dentro do container FastAPI (via lifespan) — nada de Celery/RQ no MVP
- **LLM:** nenhum — o Foxy é quem analisa; este projeto só coleta, persiste e serve dados crus
- **Banco:** PostgreSQL 16 dedicado via `pgvector/pgvector:pg16` (pgvector instalado mas sem uso no MVP — pronto pro Foxy)
- **Migrations:** Alembic 1.13.3 com seed vazio na migration inicial
- **Frontend:** Jinja2 3.1.4 + HTMX (server-side, zero JS custom, zero build step) + Tailwind CDN
- **Scraping:** Apify via httpx 0.27.2 — actors `apify/instagram-post-scraper` e `apify/instagram-profile-scraper`
- **Auth humano:** HTTP Basic (`ADMIN_USER`/`ADMIN_PASSWORD` em `/root/shared.env`)
- **Auth máquina (Foxy):** Header `X-API-Key: {FOXY_API_KEY}`
- **Deploy:** Docker Compose + Traefik (Let's Encrypt) → `https://viral-detector.srv1440237.hstgr.cloud`
- **Porta interna:** 8092 (registrada em [PADROES §2.7](../f5-vibecoding/PADROES.md))
- **Rede Docker:** própria `viral-detector_default` — Traefik em `network=host` detectando via labels (padrão F5 real)
- **Envs:** `/root/shared.env` (montado como `env_file`, padrão F5)

## 3. INVARIANTES — NÃO QUEBRAR NUNCA

Estas são as leis do sistema. Mudar qualquer uma delas exige decisão arquitetural explícita do Sandro.

### I1. Apenas dados públicos do Instagram — sempre

- Viral Detector **nunca** tenta acessar perfil privado, stories, highlights, DMs ou qualquer recurso autenticado.
- Perfil privado detectado pelo Apify → `monitored_profiles.status=private`, não tentar novamente.
- Se o requisito mudar (ex: precisar de dado privado), a responsabilidade é do [`instagram-collector`](../instagram-collector/) (OAuth autenticado), não deste projeto.

### I2. Toda chamada a API externa paga passa pelo client centralizado

- **Apify:** única via é `app/services/apify_client.py`. **Proibido** chamar `httpx.get('https://api.apify.com/...')` de qualquer outro lugar.
- **ClickUp:** única via é `app/services/clickup_client.py`.
- **Motivo:** rastreabilidade de custo, detecção centralizada de crédito esgotado ([PADROES §3.6](../f5-vibecoding/PADROES.md)), mascaramento uniforme de token em logs.
- Em revisão de PR: chamada HTTP direta a `apify.com` ou `api.clickup.com` fora do client → **rejeitar**.

### I3. Auth matrix é lei — declarada em [PRD §4](PRD.md) e espelhada em `app/core/auth.py`

| Tipo de endpoint | Basic | X-API-Key | Sem auth |
|---|:-:|:-:|:-:|
| `GET /health` | | | ✅ |
| `GET /posts/viral`, `GET /profiles/{u}` | ✅ | ✅ | |
| `GET /jobs/{id}` | ✅ | ✅ | |
| `POST /collect/now` | ✅ | | |
| `POST /jobs/resume-paused` | ✅ | | |
| `GET /` (dashboard), `GET /profiles/{u}` HTML | ✅ | | |

- **Proibido** endpoint sem decorator/dependência de auth, **exceto** `/health`.
- Adicionar endpoint novo → **atualizar esta tabela no mesmo commit**.

### I4. Custo Apify é monitorado e graceful-degraded

- Detecção de crédito esgotado: HTTP 402 OU body com `insufficient-usage` / `monthly-usage-hard-limit-exceeded` ([PADROES §3.6](../f5-vibecoding/PADROES.md)).
- Ao detectar → todos os jobs em andamento passam a `status=paused_no_credit`, nenhum job novo é disparado, alerta Telegram + banner vermelho `#DC2626` na UI.
- Retomada **apenas via ação humana deliberada**: `POST /jobs/resume-paused` (Basic Auth).
- **Proibido** retomar automaticamente via timer — é um problema humano (acertar o plano Apify), não técnico.

### I5. Mediana por tipo, sempre

- F3 calcula mediana **separada** para VIDEO/REEL, CAROUSEL_ALBUM, IMAGE.
- Posts > 180 dias pesam 50% na mediana.
- Tipo com < 10 posts → marcar `insufficient_data`, não calcular.
- **Proibido** mediana única misturando tipos — Reels inflam views e distorcem posts estáticos.

### I6. Não tocar no custom field `7d0f03c6-...` do ClickUp

- O clickup-agent