@danhachuel/leadmaster 0.1.17 → 0.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/backend/__pycache__/main.cpython-311.pyc +0 -0
- package/backend/services/__pycache__/__init__.cpython-311.pyc +0 -0
- package/backend/services/__pycache__/agents.cpython-311.pyc +0 -0
- package/backend/services/__pycache__/crm_agent.cpython-311.pyc +0 -0
- package/backend/services/__pycache__/extractors.cpython-311.pyc +0 -0
- package/backend/services/__pycache__/flags.cpython-311.pyc +0 -0
- package/backend/services/__pycache__/llm.cpython-311.pyc +0 -0
- package/backend/services/__pycache__/storage.cpython-311.pyc +0 -0
- package/backend/tests/__pycache__/conftest.cpython-311-pytest-8.4.2.pyc +0 -0
- package/backend/tests/__pycache__/test_campaigns_routes.cpython-311-pytest-8.4.2.pyc +0 -0
- package/backend/tests/__pycache__/test_cnpj_extractor.cpython-311-pytest-8.4.2.pyc +0 -0
- package/backend/tests/__pycache__/test_crm_routes.cpython-311-pytest-8.4.2.pyc +0 -0
- package/backend/tests/__pycache__/test_instagram_extractor.cpython-311-pytest-8.4.2.pyc +0 -0
- package/backend/tests/__pycache__/test_launcher_protections.cpython-311-pytest-8.4.2.pyc +0 -0
- package/backend/tests/__pycache__/test_maps_extractor.cpython-311-pytest-8.4.2.pyc +0 -0
- package/backend/tests/__pycache__/test_metrics.cpython-311-pytest-8.4.2.pyc +0 -0
- package/backend/tests/__pycache__/test_process_flags.cpython-311-pytest-8.4.2.pyc +0 -0
- package/backend/tests/test_metrics.py +112 -0
- package/frontend/dist/assets/{index-BWyDEt5a.js → index-DFVTLg-7.js} +4 -4
- package/frontend/dist/index.html +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -37,6 +37,37 @@ normaliza para a forma canônica (ex.: `0.1.0`).
|
|
|
37
37
|
falha se `process.stdin.resume();` desaparecer, se `.unref()` aparecer, se o
|
|
38
38
|
ciclo de vida vinculado for removido ou se os handlers globais sumirem.
|
|
39
39
|
|
|
40
|
+
## [0.1.18] — 2026-11-09
|
|
41
|
+
|
|
42
|
+
### Adicionado
|
|
43
|
+
|
|
44
|
+
- **Zero states no Painel (Efeito Zeigarnik)** — com base vazia a tela não
|
|
45
|
+
mostra mais gráficos em branco:
|
|
46
|
+
- `LeadsStageChart`: **"Nenhum dado ainda. Comece extraindo leads!"** com
|
|
47
|
+
orientação aos extratores (Maps/Instagram/CNPJ);
|
|
48
|
+
- `CampaignsDoughnut`: **"Nenhuma campanha disparada ainda."** com orientação
|
|
49
|
+
a Disparos;
|
|
50
|
+
- ambos em bloco tracejado discreto, sem quebrar o layout (altura preservada).
|
|
51
|
+
- **Testes das métricas (`backend/tests/test_metrics.py`)** — 4 novos testes da
|
|
52
|
+
rota `GET /api/metrics` com dados conhecidos em `LEADMASTER_HOME` temporário
|
|
53
|
+
(seeds de demo neutralizados):
|
|
54
|
+
- base vazia: totais zerados e taxas padrão (100% entrega / 0% erro);
|
|
55
|
+
- leads conhecidos: `leads_total` e `leads_por_etapa` exatos por coluna;
|
|
56
|
+
- consolidação de campanhas: enviadas/erros somados e taxas proporcionais
|
|
57
|
+
(150/162 → 92,6% e 7,4%);
|
|
58
|
+
- reflexo do Kanban: mover lead de coluna via `PUT /api/crm/leads/{id}/etapa`
|
|
59
|
+
recalcula `leads_por_etapa` na hora.
|
|
60
|
+
- Suíte completa: **44 passed**.
|
|
61
|
+
|
|
62
|
+
### Auditoria
|
|
63
|
+
|
|
64
|
+
- **`GET /api/metrics` (dinâmica):** auditada contra a especificação — já lê
|
|
65
|
+
`leads.json`/`campaigns.json` em tempo real e devolve `leads_total`,
|
|
66
|
+
`leads_por_etapa` (leads/em_contato/proposta/fechado), totais consolidados
|
|
67
|
+
das campanhas, `taxa_entrega`/`taxa_erro` (fallback 100%/0% sem campanhas) e
|
|
68
|
+
`conexao` (geral/llm/openoutreach). Nenhuma alteração de cálculo necessária —
|
|
69
|
+
agora com cobertura de testes e zero states na UI.
|
|
70
|
+
|
|
40
71
|
## [0.1.17] — 2026-11-09
|
|
41
72
|
|
|
42
73
|
### Corrigido
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Testes das métricas do Painel — GET /api/metrics.
|
|
2
|
+
|
|
3
|
+
Popula os JSONs de teste (LEADMASTER_HOME em diretório temporário) com dados
|
|
4
|
+
conhecidos e verifica o cálculo em tempo real: totais de leads, distribuição
|
|
5
|
+
por etapa, consolidação das campanhas (enviadas/erros/taxas) e status da
|
|
6
|
+
conexão.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from fastapi.testclient import TestClient
|
|
10
|
+
|
|
11
|
+
import main
|
|
12
|
+
from services import storage
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _client(tmp_path, monkeypatch):
|
|
16
|
+
monkeypatch.setenv("LEADMASTER_HOME", str(tmp_path))
|
|
17
|
+
# Cria os arquivos vazios ANTES do lifespan: evita os seeds de demo que o
|
|
18
|
+
# backend semeia na primeira execução — assim o teste parte de base zerada.
|
|
19
|
+
storage.save_leads([])
|
|
20
|
+
storage.save_campaigns([])
|
|
21
|
+
return TestClient(main.app)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _lead(nome: str, etapa: str, telefone: str) -> dict:
|
|
25
|
+
return {
|
|
26
|
+
"nome": nome,
|
|
27
|
+
"telefone": telefone,
|
|
28
|
+
"email": "",
|
|
29
|
+
"fonte": "teste",
|
|
30
|
+
"etapa": etapa,
|
|
31
|
+
"empresa": "",
|
|
32
|
+
"cidade": "",
|
|
33
|
+
"endereco": "",
|
|
34
|
+
"score": 50,
|
|
35
|
+
"tags": [],
|
|
36
|
+
"observacoes": "",
|
|
37
|
+
"extras": {},
|
|
38
|
+
"criado_em": storage.now_iso(),
|
|
39
|
+
"atualizado_em": storage.now_iso(),
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_metrics_sem_dados(tmp_path, monkeypatch):
|
|
44
|
+
# Base vazia: nada de quebrar — taxas padrão (100% entrega, 0% erro).
|
|
45
|
+
with _client(tmp_path, monkeypatch) as client:
|
|
46
|
+
r = client.get("/api/metrics")
|
|
47
|
+
assert r.status_code == 200
|
|
48
|
+
m = r.json()
|
|
49
|
+
assert m["leads_total"] == 0
|
|
50
|
+
assert m["leads_por_etapa"] == {"leads": 0, "em_contato": 0, "proposta": 0, "fechado": 0}
|
|
51
|
+
assert m["total_enviadas"] == 0
|
|
52
|
+
assert m["total_erros"] == 0
|
|
53
|
+
assert m["taxa_entrega"] == 100.0
|
|
54
|
+
assert m["taxa_erro"] == 0.0
|
|
55
|
+
assert m["campanhas"] == {"enviadas": 0, "erros": 0, "total": 0}
|
|
56
|
+
assert m["conexao"]["geral"] is True
|
|
57
|
+
assert isinstance(m["conexao"]["llm"], bool)
|
|
58
|
+
assert isinstance(m["conexao"]["openoutreach"], bool)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_metrics_com_leads_conhecidos(tmp_path, monkeypatch):
|
|
62
|
+
monkeypatch.setenv("LEADMASTER_HOME", str(tmp_path))
|
|
63
|
+
storage.save_leads([])
|
|
64
|
+
storage.save_campaigns([])
|
|
65
|
+
storage.import_leads(
|
|
66
|
+
[
|
|
67
|
+
_lead("Lead A", "leads", "(11) 90000-0001"),
|
|
68
|
+
_lead("Lead B", "leads", "(11) 90000-0002"),
|
|
69
|
+
_lead("Lead C", "em_contato", "(11) 90000-0003"),
|
|
70
|
+
_lead("Lead D", "proposta", "(11) 90000-0004"),
|
|
71
|
+
_lead("Lead E", "fechado", "(11) 90000-0005"),
|
|
72
|
+
],
|
|
73
|
+
dedup=False,
|
|
74
|
+
)
|
|
75
|
+
with TestClient(main.app) as client:
|
|
76
|
+
m = client.get("/api/metrics").json()
|
|
77
|
+
assert m["leads_total"] == 5
|
|
78
|
+
assert m["leads_por_etapa"] == {"leads": 2, "em_contato": 1, "proposta": 1, "fechado": 1}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def test_metrics_consolida_campanhas(tmp_path, monkeypatch):
|
|
82
|
+
monkeypatch.setenv("LEADMASTER_HOME", str(tmp_path))
|
|
83
|
+
storage.save_leads([])
|
|
84
|
+
storage.save_campaigns(
|
|
85
|
+
[
|
|
86
|
+
{"id": "c1", "nome": "Campanha 1", "status": "concluida", "enviadas": 90, "erros": 10},
|
|
87
|
+
{"id": "c2", "nome": "Campanha 2", "status": "concluida", "enviadas": 60, "erros": 2},
|
|
88
|
+
]
|
|
89
|
+
)
|
|
90
|
+
with TestClient(main.app) as client:
|
|
91
|
+
m = client.get("/api/metrics").json()
|
|
92
|
+
assert m["total_enviadas"] == 150
|
|
93
|
+
assert m["total_erros"] == 12
|
|
94
|
+
assert m["taxa_entrega"] == 92.6 # 150 / 162 * 100
|
|
95
|
+
assert m["taxa_erro"] == 7.4 # 12 / 162 * 100
|
|
96
|
+
assert m["campanhas"] == {"enviadas": 150, "erros": 12, "total": 2}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def test_metrics_reflete_alteracoes_no_kanban(tmp_path, monkeypatch):
|
|
100
|
+
# Mover um lead de etapa (rota do CRM) muda a contagem no /api/metrics.
|
|
101
|
+
with _client(tmp_path, monkeypatch) as client:
|
|
102
|
+
criado = client.post(
|
|
103
|
+
"/api/crm/leads",
|
|
104
|
+
json={"nome": "Lead Kanban", "telefone": "(11) 90000-0009", "fonte": "teste"},
|
|
105
|
+
).json()
|
|
106
|
+
m1 = client.get("/api/metrics").json()
|
|
107
|
+
assert m1["leads_por_etapa"]["leads"] == 1
|
|
108
|
+
|
|
109
|
+
client.put(f"/api/crm/leads/{criado['id']}/etapa", json={"etapa": "fechado"})
|
|
110
|
+
m2 = client.get("/api/metrics").json()
|
|
111
|
+
assert m2["leads_por_etapa"] == {"leads": 0, "em_contato": 0, "proposta": 0, "fechado": 1}
|
|
112
|
+
assert m2["leads_total"] == 1
|