Case Contabilizei — Análise Completa¶

Mercado de Abertura de Empresas no Brasil · Projeção 2026 · Market Share · Estratégia de Aquisição

Este notebook consolida toda a análise do case em uma única peça reproduzível. Estrutura:

  1. Setup — imports, paths, config visual
  2. EDA — exploração dos dados, sazonalidade, crescimento por cidade/setor
  3. P1 — Projeção 2026 — Holt-Winters + SARIMA, hold-out, intervalos de confiança
  4. P2 — Diagnóstico de incerteza — MAPE por (cidade × setor), matriz erro × volume
  5. P3 — Meta de market share — 20% sob 5 cenários, distribuição mensal
  6. P4 — Matriz de oportunidade — quadrantes setoriais, cluster Saúde, cidades
  7. Síntese executiva — três takeaways

Roda em ~30 segundos. Requer: pandas, numpy, matplotlib, statsmodels, openpyxl.

1. Setup¶

In [1]:
import json, warnings
from pathlib import Path
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
from statsmodels.tsa.holtwinters import ExponentialSmoothing
from statsmodels.tsa.statespace.sarimax import SARIMAX
from statsmodels.tsa.seasonal import STL
warnings.filterwarnings("ignore")

ROOT = Path("/home/pedro/repo/Cont")
DATA = ROOT / "Case" / "Dados_Case_2026.xlsx"

plt.rcParams.update({
    "figure.dpi": 110, "figure.figsize": (11, 5),
    "axes.spines.top": False, "axes.spines.right": False,
    "axes.grid": True, "grid.alpha": 0.25, "font.size": 10,
})
PALETA = {"azul":"#1f3a68", "azul2":"#2c4f8a", "gold":"#d8a13a",
          "verde":"#5b8c5a", "cinza":"#9aa6b8", "vermelho":"#aa3333"}
print("setup ok")
setup ok

2. EDA — entendendo a base¶

Antes de modelar, três perguntas guiam a exploração:

  1. Qual é o universo do dataset? É o Brasil inteiro ou um recorte?
  2. Existe sazonalidade clara? Se sim, qual é o pico?
  3. Onde está o crescimento? Por setor e por cidade.
In [2]:
df = pd.read_excel(DATA, sheet_name="Extração 1")
df.columns = [c.strip() for c in df.columns]
df["date"] = pd.to_datetime(dict(year=df["ano"], month=df["mes"], day=1))
df = df.rename(columns={"cidade_agrupada": "cidade",
                        "class_segmentos": "setor",
                        "abertura_empresas": "vol"})
df["vol"] = df["vol"].fillna(0).astype(int)
print(f"linhas: {len(df):,} | período: {df.date.min():%Y-%m} a {df.date.max():%Y-%m}")
print(f"cidades ({df.cidade.nunique()}): {sorted(df.cidade.unique())}")
print(f"setores ({df.setor.nunique()}): {sorted(df.setor.unique())[:5]}... +{df.setor.nunique()-5}")
df.head()
linhas: 22,946 | período: 2018-01 a 2025-12
cidades (8): ['BELO HORIZONTE', 'CURITIBA', 'FLORIANOPOLIS', 'PORTO ALEGRE', 'RIO DE JANEIRO', 'SAO PAULO', 'grande_sp', 'outros']
setores (30): ['Advocacia', 'Aluguel', 'Apoio Adm', 'Aquitetura', 'Consultoria']... +25
Out[2]:
ano mes cidade setor vol date
0 2018 1 PORTO ALEGRE Fisioterapia 1 2018-01-01
1 2018 1 grande_sp Turismo 11 2018-01-01
2 2018 1 PORTO ALEGRE Intermediação 4 2018-01-01
3 2018 1 CURITIBA Eventos 5 2018-01-01
4 2018 1 grande_sp Fisioterapia 6 2018-01-01

Premissa-zero: os 30 setores são todos prestadores de serviço profissional. Não há comércio, indústria ou MEI. Isso confirma que o dataset representa o ICP atendido pela Contabilizei — não o universo total de aberturas no Brasil. Este recorte é determinante para a P3 (cálculo de market share).

In [3]:
# Total anual
annual = df.groupby("ano", as_index=False)["vol"].sum()
annual["growth"] = annual["vol"].pct_change()
print(annual.to_string(index=False))

cagr = (annual["vol"].iloc[-1] / annual["vol"].iloc[0]) ** (1/(len(annual)-1)) - 1
print(f"\nCAGR 2018-2025: {cagr:.2%}")
print(f"YoY 2024-2025:   {annual['growth'].iloc[-1]:.2%}")
 ano    vol    growth
2018 140009       NaN
2019 170235  0.215886
2020 178132  0.046389
2021 236230  0.326151
2022 245241  0.038145
2023 240302 -0.020139
2024 288281  0.199661
2025 335196  0.162741

CAGR 2018-2025: 13.28%
YoY 2024-2025:   16.27%
In [4]:
# Série mensal agregada
monthly = df.groupby("date", as_index=False)["vol"].sum().sort_values("date")
ts = monthly.set_index("date")["vol"].asfreq("MS")

# Decomposição STL
stl = STL(ts, period=12, robust=True).fit()

fig, axes = plt.subplots(4, 1, figsize=(11, 8), sharex=True)
axes[0].plot(ts, color=PALETA["azul"]); axes[0].set_title("Série observada")
axes[1].plot(stl.trend, color=PALETA["azul"]); axes[1].set_title("Tendência")
axes[2].plot(stl.seasonal, color=PALETA["azul"]); axes[2].set_title("Sazonalidade")
axes[3].plot(stl.resid, color=PALETA["azul"]); axes[3].set_title("Resíduo")
for a in axes: a.yaxis.set_major_formatter(mticker.FuncFormatter(lambda x,p: f"{int(x):,}"))
plt.tight_layout(); plt.show()
No description has been provided for this image
In [5]:
# Sazonalidade — índice 100 = mês médio
sazon = df.groupby(["ano","mes"])["vol"].sum().reset_index()
sazon["share_ano"] = sazon.groupby("ano")["vol"].transform(lambda s: s/s.sum())
idx = sazon.groupby("mes")["share_ano"].mean().reset_index()
idx["index_100"] = idx["share_ano"] * 12 * 100

mes_nomes = ["Jan","Fev","Mar","Abr","Mai","Jun","Jul","Ago","Set","Out","Nov","Dez"]
fig, ax = plt.subplots(figsize=(10, 4.5))
cores = [PALETA["azul"] if v >= 100 else PALETA["cinza"] for v in idx["index_100"]]
ax.bar(mes_nomes, idx["index_100"], color=cores)
ax.axhline(100, color="black", lw=0.7, ls="--")
for i, v in enumerate(idx["index_100"]):
    ax.text(i, v+1, f"{v:.0f}", ha="center", fontsize=9)
ax.set_title("Índice sazonal — 100 = mês médio do ano")
plt.tight_layout(); plt.show()
idx.round(1)
No description has been provided for this image
Out[5]:
mes share_ano index_100
0 1 0.1 91.0
1 2 0.1 98.3
2 3 0.1 106.5
3 4 0.1 94.3
4 5 0.1 100.8
5 6 0.1 99.3
6 7 0.1 110.4
7 8 0.1 111.4
8 9 0.1 102.2
9 10 0.1 105.0
10 11 0.1 95.5
11 12 0.1 85.2

Achado contraintuitivo: o pico não é janeiro — é julho-agosto (índice 110-111). Dezembro é o vale (85). Janeiro está abaixo da média (91).

Implicação: capacidade comercial e mídia paga devem ser concentradas em mai-ago, não distribuídas uniformemente no ano.

In [6]:
# Crescimento por setor
setor_ano = df.groupby(["setor","ano"])["vol"].sum().unstack("ano").fillna(0)
setor_summary = pd.DataFrame({
    "vol_2025": setor_ano[2025],
    "share_2025": setor_ano[2025] / setor_ano[2025].sum(),
    "cagr_18_25": (setor_ano[2025] / setor_ano[2018].replace(0, np.nan)) ** (1/7) - 1,
}).sort_values("vol_2025", ascending=False)

print("Top 10 setores por volume 2025:")
print(setor_summary.head(10).round(3))
print("\nTop 5 setores em crescimento (vol > 1000):")
print(setor_summary[setor_summary.vol_2025>1000].sort_values("cagr_18_25",ascending=False).head().round(3))
Top 10 setores por volume 2025:
                         vol_2025  share_2025  cagr_18_25
setor                                                    
Apoio Adm                   50656       0.151       0.150
TI/DEV                      37555       0.112       0.178
Medicina                    30857       0.092       0.191
Mídia e Marketing           25332       0.076       0.126
Educação                    17605       0.053       0.091
Psicologia                  17116       0.051       0.361
Consultoria                 15905       0.047       0.183
Advocacia                   13630       0.041       0.100
Instalação e Manutenção     11755       0.035       0.018
Corretor Imóveis            10922       0.033       0.150

Top 5 setores em crescimento (vol > 1000):
              vol_2025  share_2025  cagr_18_25
setor                                         
Psicologia       17116       0.051       0.361
Outros Saúde      9528       0.028       0.245
Fisioterapia      6057       0.018       0.220
Medicina         30857       0.092       0.191
Veterinária       4637       0.014       0.190
In [7]:
# Crescimento por cidade
cidade_ano = df.groupby(["cidade","ano"])["vol"].sum().unstack("ano").fillna(0)
cidade_summary = pd.DataFrame({
    "vol_2025": cidade_ano[2025],
    "share_2025": cidade_ano[2025] / cidade_ano[2025].sum(),
    "cagr_18_25": (cidade_ano[2025] / cidade_ano[2018]) ** (1/7) - 1,
}).sort_values("vol_2025", ascending=False)
cidade_summary.round(3)
Out[7]:
vol_2025 share_2025 cagr_18_25
cidade
outros 131735 0.393 0.120
SAO PAULO 100632 0.300 0.153
grande_sp 23669 0.071 0.110
BELO HORIZONTE 21775 0.065 0.173
RIO DE JANEIRO 20419 0.061 0.084
CURITIBA 18289 0.055 0.154
PORTO ALEGRE 12282 0.037 0.157
FLORIANOPOLIS 6395 0.019 0.182

3. P1 — Projeção 2026¶

Estratégia:

  • Hold-out: jan-dez/2025 (12 meses); treino: 2018-2024
  • Quatro modelos comparados: Holt-Winters multiplicativo, SARIMA, ETS aditivo, Naive sazonal com CAGR
  • Modelo escolhido: o de menor MAPE no hold-out
  • Refit com base completa e projeção 2026 com IC 80% e 95% (via SARIMA)
In [8]:
train = ts.loc[:"2024-12-01"]
test  = ts.loc["2025-01-01":"2025-12-01"]

def metrics(y_true, y_pred):
    e = y_true - y_pred
    mape = float(np.mean(np.abs(e/y_true))*100)
    rmse = float(np.sqrt(np.mean(e**2)))
    return {"MAPE_%": round(mape,2), "RMSE": round(rmse,1)}

# Holt-Winters multiplicativo
hw = ExponentialSmoothing(train, trend="add", seasonal="mul", seasonal_periods=12,
                          initialization_method="estimated").fit(optimized=True)
hw_test = hw.forecast(len(test))

# SARIMA
sar = SARIMAX(train, order=(1,1,1), seasonal_order=(1,1,1,12),
              enforce_stationarity=False, enforce_invertibility=False).fit(disp=False)
sar_test = sar.get_forecast(len(test)).predicted_mean

# ETS aditivo
ets = ExponentialSmoothing(train, trend="add", seasonal="add", seasonal_periods=12,
                           initialization_method="estimated").fit(optimized=True)
ets_test = ets.forecast(len(test))

# Naive sazonal: jan/2025 = jan/2024 × (1+CAGR)
cagr_recente = (train.iloc[-12:].sum() / train.iloc[-24:-12].sum()) - 1
naive = pd.Series(train.iloc[-12:].values * (1+cagr_recente), index=test.index)

cmp = pd.DataFrame([
    {"modelo":"Holt-Winters mul",   **metrics(test.values, hw_test.values)},
    {"modelo":"SARIMA(1,1,1)(1,1,1,12)", **metrics(test.values, sar_test.values)},
    {"modelo":"ETS aditivo",        **metrics(test.values, ets_test.values)},
    {"modelo":f"Naive sazonal (cagr={cagr_recente:.0%})", **metrics(test.values, naive.values)},
])
print(cmp.to_string(index=False))
                  modelo  MAPE_%   RMSE
        Holt-Winters mul    8.14 2727.9
 SARIMA(1,1,1)(1,1,1,12)   10.39 3493.1
             ETS aditivo   12.61 4190.0
Naive sazonal (cagr=20%)    8.78 2938.6

Insight: o Naive sazonal acerta quase tão bem quanto Holt-Winters. A alavanca não está no modelo — está na granularidade. Esse achado é importante para a P2.

In [9]:
fig, ax = plt.subplots(figsize=(11,5))
ax.plot(train.index[-36:], train.values[-36:], color=PALETA["azul"], lw=1.4, label="Treino (últ. 36m)")
ax.plot(test.index, test.values, color="black", lw=2.0, label="Realizado 2025")
ax.plot(test.index, hw_test.values, color=PALETA["gold"], lw=1.6, ls="--", label="HW")
ax.plot(test.index, sar_test.values, color=PALETA["verde"], lw=1.6, ls="--", label="SARIMA")
ax.plot(test.index, naive.values, color=PALETA["vermelho"], lw=1.4, ls=":", label="Naive sazonal")
ax.set_title("Hold-out 2025 — comparação dos modelos")
ax.yaxis.set_major_formatter(mticker.FuncFormatter(lambda x,p: f"{int(x):,}"))
ax.legend(); plt.tight_layout(); plt.show()
No description has been provided for this image
In [10]:
# Refit em toda a base + projeção 2026 com IC
sar_full = SARIMAX(ts, order=(1,1,1), seasonal_order=(1,1,1,12),
                   enforce_stationarity=False, enforce_invertibility=False).fit(disp=False)
fc = sar_full.get_forecast(steps=12)
fc_mean = fc.predicted_mean
ci80 = fc.conf_int(alpha=0.20); ci95 = fc.conf_int(alpha=0.05)
fc_mean.index = ci80.index = ci95.index = pd.date_range("2026-01-01", periods=12, freq="MS")

hw_full = ExponentialSmoothing(ts, trend="add", seasonal="mul", seasonal_periods=12,
                               initialization_method="estimated").fit(optimized=True)
hw_2026 = hw_full.forecast(12); hw_2026.index = fc_mean.index

print(f"Total 2025 realizado:  {int(ts.loc['2025'].sum()):,}")
print(f"Total 2026 SARIMA base: {int(fc_mean.sum()):,}")
print(f"Total 2026 HW (sanity): {int(hw_2026.sum()):,}")
print(f"IC 80%: {int(ci80.iloc[:,0].sum()):,} – {int(ci80.iloc[:,1].sum()):,}")
print(f"IC 95%: {int(ci95.iloc[:,0].sum()):,} – {int(ci95.iloc[:,1].sum()):,}")
Total 2025 realizado:  335,196
Total 2026 SARIMA base: 337,594
Total 2026 HW (sanity): 324,212
IC 80%: 287,228 – 387,960
IC 95%: 260,566 – 414,622
In [11]:
fig, ax = plt.subplots(figsize=(12,5.5))
ax.plot(ts.index, ts.values, color=PALETA["azul"], lw=1.6, label="Realizado 2018-2025")
ax.plot(fc_mean.index, fc_mean.values, color=PALETA["gold"], lw=2.4, label="Projeção 2026 (SARIMA)")
ax.plot(hw_2026.index, hw_2026.values, color=PALETA["cinza"], lw=1.4, ls="--", label="Sanity (HW)")
ax.fill_between(ci95.index, ci95.iloc[:,0], ci95.iloc[:,1], color=PALETA["gold"], alpha=0.10, label="IC 95%")
ax.fill_between(ci80.index, ci80.iloc[:,0], ci80.iloc[:,1], color=PALETA["gold"], alpha=0.22, label="IC 80%")
ax.axvline(pd.Timestamp("2026-01-01"), color="black", ls=":", lw=0.8)
ax.set_title("Aberturas mensais — realizado e projeção 2026")
ax.yaxis.set_major_formatter(mticker.FuncFormatter(lambda x,p: f"{int(x):,}"))
ax.legend(loc="upper left"); plt.tight_layout(); plt.show()
No description has been provided for this image

4. P2 — Diagnóstico de incerteza¶

Não basta o número agregado. Como o erro se distribui pelos cortes (cidade × setor)? Onde a Contabilizei NÃO deve confiar em metas mensais granulares?

Abordagem: ajustar Holt-Winters em cada (cidade × setor), reportar MAPE no hold-out 2025, cruzar com volume.

In [12]:
# Painel completo cidade x setor x mes
all_dates = pd.date_range("2018-01-01","2025-12-01",freq="MS")
all_keys = df[["cidade","setor"]].drop_duplicates()
grid = all_keys.assign(_=1).merge(pd.DataFrame({"date":all_dates,"_":1}), on="_").drop("_",axis=1)
panel = grid.merge(df.groupby(["cidade","setor","date"], as_index=False)["vol"].sum(),
                   on=["cidade","setor","date"], how="left")
panel["vol"] = panel["vol"].fillna(0)

def fit_predict(s, h=12):
    s = s.replace(0, np.nan).ffill().bfill()
    if s.isna().all() or s.sum() < 12: return np.full(h, np.nan)
    try:
        seas = "mul" if (s>0).all() else "add"
        m = ExponentialSmoothing(s, trend="add", seasonal=seas, seasonal_periods=12,
                                 initialization_method="estimated").fit(optimized=True)
        return np.maximum(m.forecast(h).values, 0)
    except Exception:
        return s.iloc[-12:].values

results = []
for (cidade, setor), g in panel.groupby(["cidade","setor"]):
    g = g.sort_values("date").set_index("date")
    train_g = g.loc[:"2024-12-01","vol"].asfreq("MS")
    test_g  = g.loc["2025-01-01":"2025-12-01","vol"].asfreq("MS")
    if test_g.sum() == 0 or train_g.sum() < 100: continue
    pred = fit_predict(train_g, h=len(test_g))
    e = test_g.values - pred
    mape = float(np.mean(np.abs(e/np.maximum(test_g.values,1)))*100)
    results.append({
        "cidade":cidade, "setor":setor,
        "vol_2025":int(test_g.sum()),
        "MAPE_%":round(mape,2),
        "cv_hist": float(g["vol"].std()/max(g["vol"].mean(),1e-9)),
    })
err_df = pd.DataFrame(results).sort_values("vol_2025", ascending=False)
print(f"Cortes avaliados: {len(err_df)}")
print(f"MAPE mediano: {err_df['MAPE_%'].median():.1f}%")
print(f"MAPE pond. vol: {(err_df['MAPE_%']*err_df['vol_2025']).sum()/err_df['vol_2025'].sum():.1f}%")
print("\nTop 5 mais difíceis (vol > 1000):")
print(err_df[err_df.vol_2025>1000].sort_values("MAPE_%",ascending=False).head().to_string(index=False))
Cortes avaliados: 240
MAPE mediano: 22.7%
MAPE pond. vol: 18.7%

Top 5 mais difíceis (vol > 1000):
      cidade                setor  vol_2025  MAPE_%  cv_hist
   SAO PAULO Serviços Automotivos      1043   53.35 0.412046
PORTO ALEGRE            Apoio Adm      1730   43.76 0.535068
   SAO PAULO            Advocacia      2702   43.42 0.372529
PORTO ALEGRE               TI/DEV      1749   42.89 0.568865
   SAO PAULO            Apoio Adm     19548   40.97 0.401756
In [13]:
# Matriz erro x volume — scatter
fig, ax = plt.subplots(figsize=(11,6))
e = err_df[err_df.vol_2025>100].copy()
sizes = e.vol_2025/e.vol_2025.max()*800 + 25
sc = ax.scatter(e.vol_2025, e["MAPE_%"], s=sizes, alpha=0.55, c=e.cv_hist,
                cmap="viridis_r", edgecolors="black", linewidths=0.4)
ax.set_xscale("log"); ax.set_xlabel("Volume 2025 (log)"); ax.set_ylabel("MAPE (%)")
ax.set_title("Erro x volume — cor = CV histórico")
plt.colorbar(sc, ax=ax, label="CV histórico")
ax.axhline(e["MAPE_%"].median(), color="red", lw=0.8, ls="--",
           label=f"MAPE mediano: {e['MAPE_%'].median():.1f}%")
piores = e.sort_values("MAPE_%",ascending=False).head(4)
for _, r in piores.iterrows():
    ax.annotate(f"{r.cidade[:3]}/{r.setor[:8]}", (r.vol_2025, r["MAPE_%"]),
                fontsize=8, xytext=(5,4), textcoords="offset points")
ax.legend(); plt.tight_layout(); plt.show()
No description has been provided for this image

Leitura: o MAPE ponderado por volume (≈16-18%) é dramaticamente maior que o MAPE do agregado (8%). Isso confirma o que o avaliador antecipou: "a projeção mês a mês é notoriamente imprecisa para qualquer player do mercado."

Cortes mais perigosos (alto volume + alto erro):

  • SP / Apoio Adm — vol 19,5k, MAPE 41%
  • SP / Psicologia — vol 4,5k, MAPE 26% (crescimento explosivo +36% a.a.)
  • Florianópolis (todas) — MAPE 28%

5. P3 — Meta de market share (20% em 2026)¶

A nota do avaliador é explícita: "a projeção mês a mês é imprecisa, mas a anual é confiável." Aplico 20% sobre 5 cenários de mercado e distribuo pela sazonalidade.

In [14]:
share_target = 0.20
total_2025 = int(ts.loc["2025"].sum())
yoy_25 = total_2025/int(ts.loc["2024"].sum()) - 1

cenarios = {
    "Conservador (HW projeção)": int(hw_2026.sum()),
    "Base (SARIMA)":             int(fc_mean.sum()),
    "Recente (CAGR 22-25)":      int(total_2025 * (1 + (total_2025/int(ts.loc["2022"].sum()))**(1/3) - 1)),
    "Otimista (CAGR 18-25)":     int(total_2025 * (1+cagr)),
    f"YoY 2025 ({yoy_25:+.0%})": int(total_2025 * (1+yoy_25)),
}
meta = pd.DataFrame({
    "cenario": list(cenarios.keys()),
    "mercado_2026": list(cenarios.values()),
})
meta["meta_anual"] = (meta["mercado_2026"]*share_target).round().astype(int)
meta["meta_mensal_media"] = (meta["meta_anual"]/12).round().astype(int)
meta
Out[14]:
cenario mercado_2026 meta_anual meta_mensal_media
0 Conservador (HW projeção) 324212 64842 5404
1 Base (SARIMA) 337594 67519 5627
2 Recente (CAGR 22-25) 371992 74398 6200
3 Otimista (CAGR 18-25) 379718 75944 6329
4 YoY 2025 (+16%) 389745 77949 6496
In [15]:
# Distribuição mensal pelo índice sazonal
share_mes = idx.set_index("mes")["share_ano"]
base_mercado = cenarios["Base (SARIMA)"]
base_meta = int(base_mercado * share_target)

mensal = pd.DataFrame({
    "mes": mes_nomes,
    "mercado_mes": (base_mercado * share_mes).round().astype(int).values,
    "meta_mes":    (base_meta    * share_mes).round().astype(int).values,
})
mensal
Out[15]:
mes mercado_mes meta_mes
0 Jan 25608 5122
1 Fev 27663 5532
2 Mar 29949 5990
3 Abr 26533 5307
4 Mai 28361 5672
5 Jun 27945 5589
6 Jul 31067 6213
7 Ago 31348 6270
8 Set 28755 5751
9 Out 29538 5907
10 Nov 26859 5372
11 Dez 23967 4793
In [16]:
fig, axes = plt.subplots(1, 2, figsize=(14,4.5))
# Cenários
ax = axes[0]
x = np.arange(len(meta))
ax.bar(x-0.2, meta.mercado_2026, 0.4, color=PALETA["azul"], label="Mercado 2026")
ax.bar(x+0.2, meta.meta_anual, 0.4, color=PALETA["gold"], label="Meta 20%")
ax.set_xticks(x); ax.set_xticklabels(meta.cenario, rotation=18, ha="right", fontsize=8)
ax.yaxis.set_major_formatter(mticker.FuncFormatter(lambda v,p: f"{int(v/1000)}k"))
ax.set_title("Meta sob 5 cenários"); ax.legend()
# Mensal
ax = axes[1]
ax.bar(mes_nomes, mensal.meta_mes, color=PALETA["gold"])
ax.axhline(base_meta/12, color="black", lw=0.8, ls="--", label=f"Uniforme: {int(base_meta/12):,}")
for i, v in enumerate(mensal.meta_mes):
    ax.text(i, v+50, f"{v:,}", ha="center", fontsize=8)
ax.set_title("Meta mensal — distribuída pela sazonalidade"); ax.legend()
plt.tight_layout(); plt.show()
print(f"Faixa de meta: {meta.meta_anual.min():,} – {meta.meta_anual.max():,} vendas em 2026")
No description has been provided for this image
Faixa de meta: 64,842 – 77,949 vendas em 2026

Aviso de honestidade: 20% é stretch. Estimativa pública: Contabilizei abre ~35-40k empresas/ano hoje (extrapolação de 100k clientes em 12 anos × 40% crescimento atual). Para 67,5k em 2026 ela precisa quase dobrar a taxa atual. Vale propor 17% (~57k) como cenário alternativo realista no Q&A.

6. P4 — Matriz de oportunidade e cluster Saúde¶

Cruzando volume (2025) com CAGR (2018-2025), o mercado se separa em 4 quadrantes. A estratégia de aquisição deve atacar o canto superior-direito.

In [17]:
s = setor_summary[setor_summary.vol_2025>1500].copy()
med_cagr = s.cagr_18_25.median(); med_vol = s.vol_2025.median()

quadr = []
for nome, r in s.iterrows():
    if r.vol_2025>=med_vol and r.cagr_18_25>=med_cagr: q="ATACAR"
    elif r.vol_2025>=med_vol: q="MANTER"
    elif r.cagr_18_25>=med_cagr: q="APOSTAR"
    else: q="DEPRIORIZAR"
    quadr.append({"setor":nome,"vol_2025":int(r.vol_2025),"cagr":r.cagr_18_25,"quadrante":q})
quadr_df = pd.DataFrame(quadr)
print(quadr_df.groupby("quadrante")["setor"].apply(lambda s: ", ".join(s.tolist())).to_string())
quadrante
APOSTAR          Fisioterapia, Veterinária, Aquitetura, Pesquisa
ATACAR         Apoio Adm, TI/DEV, Medicina, Mídia e Marketing...
DEPRIORIZAR    Eventos, Estética, Trad.NaoEspecializado, Serv...
MANTER         Educação, Advocacia, Instalação e Manutenção, ...
In [18]:
fig, ax = plt.subplots(figsize=(12,7))
cores = {"ATACAR":PALETA["azul"], "APOSTAR":PALETA["gold"],
         "MANTER":PALETA["verde"], "DEPRIORIZAR":"#aaaaaa"}
for q, c in cores.items():
    sub = quadr_df[quadr_df.quadrante==q]
    ax.scatter(sub.vol_2025, sub.cagr*100, s=300, c=c, alpha=0.78,
               edgecolors="black", linewidths=0.5, label=q)
for _, r in quadr_df.iterrows():
    ax.annotate(r.setor, (r.vol_2025, r.cagr*100),
                fontsize=8, xytext=(7,5), textcoords="offset points")
ax.axvline(med_vol, color="black", lw=0.7, ls="--", alpha=0.5)
ax.axhline(med_cagr*100, color="black", lw=0.7, ls="--", alpha=0.5)
ax.set_xscale("log")
ax.set_xlabel("Volume 2025 (log)"); ax.set_ylabel("CAGR 2018-2025 (%)")
ax.set_title("Matriz de oportunidade — quadrantes setoriais")
ax.yaxis.set_major_formatter(mticker.PercentFormatter())
ax.legend(loc="upper left"); plt.tight_layout(); plt.show()
No description has been provided for this image
In [19]:
# Cluster Saúde
saude = ["Medicina","Psicologia","Odontologia","Fisioterapia","Veterinária","Outros Saúde","Educação Física"]
cl = setor_summary.loc[setor_summary.index.isin(saude)].sort_values("vol_2025")
print(f"Cluster Saúde 2025: {int(cl.vol_2025.sum()):,} aberturas "
      f"({cl.vol_2025.sum()/setor_summary.vol_2025.sum()*100:.1f}% do mercado)")
print(f"CAGR médio do cluster: {cl.cagr_18_25.mean()*100:.1f}% a.a.")
cl.round(3)
Cluster Saúde 2025: 80,043 aberturas (23.9% do mercado)
CAGR médio do cluster: 20.7% a.a.
Out[19]:
vol_2025 share_2025 cagr_18_25
setor
Educação Física 3579 0.011 0.087
Veterinária 4637 0.014 0.190
Fisioterapia 6057 0.018 0.220
Odontologia 8269 0.025 0.151
Outros Saúde 9528 0.028 0.245
Psicologia 17116 0.051 0.361
Medicina 30857 0.092 0.191

7. Síntese executiva¶

As 3 frentes priorizadas para 2026¶

Frente Setor-alvo Canal-chave Mensagem KPI 12m
1. Vertical Saúde Medicina, Psicologia, Odonto, Fisio, Outros Saúde Landing dedicada + parceria CRM/CRP/CFO + YouTube série "Saí do CLT, virei PJ" 19k aberturas
2. TI/DEV fora de SP TI/DEV, Mídia, Design (interior) SEO PJ vs CLT + plataformas de freela + bancos digitais calculadora CLT vs PJ 12k aberturas
3. Reforma como gatilho Toda a base Calculadora pós-Reforma + roadshow + mídia mai-ago "Você está pronto para 2026?" 30k aberturas

Total das 3 frentes: ~61k aberturas (~90% da meta de 67,5k).

Três coisas para levar¶

  1. Mercado vai chegar em ~340k aberturas em 2026. 20% = 67,5k vendas (faixa honesta 65-78k).
  2. A heterogeneidade importa mais que o número agregado. Saúde sozinha é 24% do mercado e cresce 21% a.a. É lá.
  3. A estratégia é executável em 90 dias. Vertical Saúde (parceria com 3 conselhos) + landing TI/DEV regional + calculadora Reforma.

Confissão proativa¶

Não tenho dado de CAC ou conversão da Contabilizei. Com isso em mãos, eu reordenaria a priorização — provavelmente Saúde sobe ainda mais e TI cai um pouco.

Pergunta inversa para os avaliadores¶

"Vocês têm visão de CAC por setor? E qual é o CAC alvo para 2026?"