#!/usr/bin/env python3
# Painel "Saída de Pauta · Venda" no formato do perfil-lead-site.html (referência aprovada pelo COO)
import json
from datetime import datetime
SP = "/private/tmp/claude-501/-Users-luizh-souza/28aa3f80-7e4d-40f1-9500-a33d868d547f/scratchpad"
ref = open(f"{SP}/perfil-lead-site.html", encoding="utf-8").read()
dados = json.load(open(f"{SP}/dados.json", encoding="utf-8"))
head = ref[ref.index("
"): ref.index("") + len("")]
head = head.replace("Perfil do Lead · Site · IntelBuzz BI",
"Saída de Pauta · Venda · IntelBuzz BI")
fmtR = lambda v: ("R$ -" if v < 0 else "R$ ") + f"{abs(round(v)):,}".replace(",", ".")
fmtI = lambda v: f"{round(v):,}".replace(",", ".")
pct1 = lambda v: str(round(v, 1)).replace(".", ",") + "%"
m = dados["mensal"]
tot_e = sum(x["e_qtd"] for x in m); tot_ev = sum(x["e_vgv"] for x in m)
tot_s = sum(x["s_qtd"] for x in m); tot_sv = sum(x["s_vgv"] for x in m)
saldo_v = tot_ev - tot_sv
mot = {x["m"]: x for x in dados["motivos"]}
vt, vi = mot["Vendido Terceiros"], mot["Vendido Imobiliária"]
decid = vt["qtd"] + vi["qtd"]
taxa_q = 100 * vi["qtd"] / decid
taxa_v = 100 * vi["vgv"] / (vi["vgv"] + vt["vgv"])
MESES = {"2026-05": "Maio", "2026-06": "Junho", "2026-07": "Julho"}
CLASSE = {"Vendido Terceiros": ("Perdido", "ruim", "#B23A2E"), "Alugado Terceiros": ("Perdido", "ruim", "#B23A2E"),
"Vendido Imobiliária": ("Capturado", "boa", "#2E7D52"), "Alugado Imobiliária": ("Capturado", "boa", "#2E7D52")}
jul = next(x for x in dados["captura_mes"] if x["mes"] == "2026-07")
semeq = next(x for x in dados["equipes"] if x["eq"] == "Sem equipe")
susp = mot["Suspenso"]
def pill(v, boa=50, neutra=25):
cls = "boa" if v >= boa else ("neutra" if v >= neutra else "ruim")
return f"{pct1(v)}"
def saldo_pill(v):
cls = "boa" if v >= 0 else "ruim"
return f"{fmtR(v)}"
# ── Nº 01 Balanço mensal ──
tb1 = "| Mês | Entradas | VGV entrada | Saídas | VGV saída | Saldo de VGV |
"
for x in m:
sv = x["e_vgv"] - x["s_vgv"]
tb1 += (f"| {MESES[x['mes']]} | {x['e_qtd']} | "
f"{fmtR(x['e_vgv'])} | {x['s_qtd']} | "
f"{fmtR(x['s_vgv'])} | {saldo_pill(sv)} |
")
tb1 += (f"| Trimestre | {tot_e} | {fmtR(tot_ev)} | "
f"{tot_s} | {fmtR(tot_sv)} | {saldo_pill(saldo_v)} |
")
# ── Nº 02 Captura ──
tb2 = "| Mês | Buzz sobre decididas | Captura por imóveis | Captura por VGV |
"
for x in dados["captura_mes"]:
dq = x["vi"] + x["vt"]; dv = x["vi_vgv"] + x["vt_vgv"]
tb2 += (f"| {MESES[x['mes']]} | {x['vi']}"
f" de {dq} | "
f"{pill(100*x['vi']/dq)} | {pill(100*x['vi_vgv']/dv)} |
")
tb2 += (f"| Trimestre | {vi['qtd']} de {decid} | "
f"{pill(taxa_q)} | {pill(taxa_v)} |
")
# ── Nº 03 Motivos ──
max_share_m = max(100 * (x["vgv"] or 0) / tot_sv for x in dados["motivos"])
tb3 = "| Motivo | Share do VGV de saída | Imóveis | VGV | Classe |
"
for x in sorted(dados["motivos"], key=lambda r: -(r["vgv"] or 0)):
share = 100 * (x["vgv"] or 0) / tot_sv
nome_cls, pcls, cor = CLASSE.get(x["m"], ("Neutro", "neutra", "#8A8D8E"))
apagado = " apagado" if x["m"] in ("Voltou para pauta", "(em apuração)") else ""
tb3 += (f"| {x['m']} | "
f" | "
f"{x['qtd']} | {fmtR(x['vgv'] or 0)} | "
f"{nome_cls} |
")
# ── Nº 04 Equipes ──
eqs = [x for x in dados["equipes"]]
max_share_e = max(max(100*x["e_vgv"]/tot_ev, 100*x["s_vgv"]/tot_sv) for x in eqs)
tb4 = "| Equipe | Share do VGV ent/sai | Entradas / Saídas | VGV entrada | VGV saída | Saldo |
"
for x in sorted(eqs, key=lambda r: -r["s_vgv"]):
se, ss = 100 * x["e_vgv"] / tot_ev, 100 * x["s_vgv"] / tot_sv
tb4 += (f"| {x['eq']} | "
f" | "
f"{x['e_qtd']}/{x['s_qtd']} | "
f"{fmtR(x['e_vgv'])} | {fmtR(x['s_vgv'])} | "
f"{saldo_pill(x['e_vgv'] - x['s_vgv'])} |
")
# ── Nº 05 Corretores (merge entrada+saída, top por saída) ──
corr = {}
for x in dados["corr_ent"]:
corr.setdefault(x["nome"], {"e_qtd": 0, "e_vgv": 0, "s_qtd": 0, "s_vgv": 0})
corr[x["nome"]]["e_qtd"] += x["qtd"]; corr[x["nome"]]["e_vgv"] += x["vgv"] or 0
for x in dados["corr_sai"]:
corr.setdefault(x["nome"], {"e_qtd": 0, "e_vgv": 0, "s_qtd": 0, "s_vgv": 0})
corr[x["nome"]]["s_qtd"] += x["qtd"]; corr[x["nome"]]["s_vgv"] += x["vgv"] or 0
rank = sorted(corr.items(), key=lambda kv: -kv[1]["s_vgv"])
top, resto = rank[:14], rank[14:]
max_share_c = max(100 * v["s_vgv"] / tot_sv for _, v in top)
tb5 = "| Corretor | Share do VGV de saída | Entradas / Saídas | VGV entrada | VGV saída | Saldo |
"
def linha_corr(nome, v, apagado=""):
ss = 100 * v["s_vgv"] / tot_sv
return (f"| {nome} | "
f" | "
f"{v['e_qtd']}/{v['s_qtd']} | "
f"{fmtR(v['e_vgv'])} | {fmtR(v['s_vgv'])} | "
f"{saldo_pill(v['e_vgv'] - v['s_vgv'])} |
")
for nome, v in top:
tb5 += linha_corr(nome, v)
outros = {"e_qtd": sum(v["e_qtd"] for _, v in resto), "e_vgv": sum(v["e_vgv"] for _, v in resto),
"s_qtd": sum(v["s_qtd"] for _, v in resto), "s_vgv": sum(v["s_vgv"] for _, v in resto)}
tb5 += linha_corr(f"Outros ({len(resto)} corretores)", outros, "apagado")
# ── Nº 06 Imóveis perdidos ──
tb6 = " | Código | Bairro | Agenciador | Saiu em | Valor |
"
for i, p in enumerate(dados["perd_top"], 1):
dia = "/".join(reversed(p["dia"].split("-")[1:])) + ""
dia = p["dia"][8:10] + "/" + p["dia"][5:7]
tb6 += (f"| {i}º | {p['codigo']} | "
f"{p['bairro'] or 'Sem bairro'} | {p['nome'] or 'Não informado'} | "
f"{dia} | {fmtR(p['vgv'])} |
")
gerado = datetime.now().strftime("%d/%m/%Y %H:%M")
body = f"""
Fonte: Vista Tracker · eventos de entrada e baixa da pauta de venda · base corrigida do rótulo "Voltou para pauta"
Gerado em {gerado}
01Imóveis: entrou x saiuTrimestre
416 → 348
02Valor: entrou x saiuTrimestre
{fmtR(saldo_v)}
03O mercado vendeu no nosso lugarPerda
203 imóveis
04Quando vende, quem vende?Captura
{pct1(taxa_q)} Buzz
"A pauta cresceu 68 imóveis no trimestre. Então por que o valor da carteira está caindo?"
Porque o mercado está vendendo o que a Buzz capta. Entraram {fmtR(tot_ev)} e saíram {fmtR(tot_sv)}: de cada R$ 100 que saíram, {pct1(100*vt['vgv']/tot_sv).replace('%','')} foram imóveis vendidos por terceiros. A Buzz disputou {decid} vendas decididas dentro da própria pauta e ganhou {vi['qtd']} ({pct1(taxa_q)}). A reposição segura a quantidade, não segura o valor.
- Julho foi o pior mês do trimestre, não o melhor: 80 imóveis vendidos por terceiros ({fmtR(jul['vt_vgv'])}) e captura de {pct1(100*jul['vi']/(jul['vi']+jul['vt']))}. A versão anterior deste dado mostrava 42 "retornos pra pauta" em julho; era defeito de classificação, esses imóveis saíram de verdade.
- {fmtR(semeq['s_vgv'])} saíram sem equipe atribuída ({pct1(100*semeq['s_vgv']/tot_sv)} de tudo que saiu). Sem responsável não há SLA, cadência nem reação. Atribuição de equipe deve ser condição de permanência na pauta.
- Suspenso é a segunda maior saída e segue caixa preta: {susp['qtd']} imóveis, {fmtR(susp['vgv'])}, sem motivo declarado. O formulário de saída do CRM existe exatamente pra isso: motivo declarado pelo corretor em toda baixa.
- Ação de retenção: os 15 maiores imóveis perdidos somam {fmtR(sum(p['vgv'] for p in dados['perd_top']))}. Imóvel acima de R$ 2.000.000 com risco de saída precisa de contato com proprietário, revisão de preço e plano de divulgação antes da baixa.
O balanço mês a mêsEntradas, saídas e captura por mês
Balanço mensaleventos com valor de venda
Captura nas vendas decididasVendido Imobiliária sobre Imobiliária + Terceiros
Suspensos e demais baixas ficam fora da conta porque não são disputa de venda. Régua do painel /buzz-vista: verde acima de 50%, cinza entre 25% e 50%, vermelho abaixo de 25%.
Por onde o valor saiMotivo da baixa e responsabilidade por equipe
EntradasSaídasShare = % do VGV total de entrada ou de saída do trimestre
Motivos da saídastatus real do imóvel após a baixa
Entrada e saída por equipeordenado por VGV de saída
Quem move a pautaCorretores e os imóveis de maior valor perdidos
Corretorestop 14 por VGV de saída · entradas na mesma linha
Maiores imóveis perdidos para terceirostop 15 do trimestre
MétodoRéguas e correção da base
Como este painel foi montado
- Entradas são eventos de novo agenciamento ou novo cadastro com valor de venda no Vista Tracker. Saídas são baixas da pauta de venda. Eventos revertidos pela reconciliação não entram. O histórico do tracker inicia em 11/05/2026, limitação que afeta o começo de maio.
- Motivo da saída vem do status real do imóvel no Vista após a baixa (via warehouse). "Suspenso" e "(em apuração)" não têm motivo declarado pelo corretor; a declaração estruturada é a fila do CRM em crm.intelbuzz.com.br/saidas.
- Correção de 10/08/2026: o rótulo "Voltou para pauta" era atribuído por engano quando o warehouse (atrasado um dia) ainda mostrava o imóvel ativo. Das 111 ocorrências históricas, 107 eram falsas e foram reclassificadas com o motivo real. Versões anteriores deste relatório subestimavam a perda para terceiros.
- Sem equipe: imóvel cujo agenciador não está mapeado em nenhuma equipe no cadastro de equipes do tracker.
"""
html = "\n\n" + head + "\n\n" + body
open(f"{SP}/painel_pauta2.html", "w", encoding="utf-8").write(html)
print("ok", len(html))