Menuabrir
EstávelAtualizado em 14 de mai. de 2026, 00:32

Este módulo depende de

2
  • crmPopula crm_contact.attribution_* em attach_attribution()
  • trackingcontact_touchpoint referencia tracking_event via event_id

Módulos que dependem deste

6
  • linksappend_touchpoint(channel='website', source_module='links') em click
  • youtubeCria attribution_campaign(kind=organic_youtube) via attribution_bridge
  • formsResolve UTM no submit pra atribuição de conversão
  • analyticssync_attribution_campaigns cria campaigns por novo source GA4
  • crmResolve UTM via attach_attribution() em create_contact
  • marketingPlugins criam attribution_campaign via attribution_bridge

Attribution

Status: 🟢 Estável (Sprint 1.A deployada) Code: backend/app/modules/attribution UI: frontend/src/app/(admin)/[slug]/admin/marketing (campaigns dentro do hub) Última revisão deste doc: 2026-05-13 por Felipe + Claude Dependências fortes: crm (popula crm_contact.attribution_*), marketing (plugins criam campaigns), tracking (eventos)


1. Identidade

O que faz (uma frase)

Persiste campanhas de marketing (paid + organic) e touchpoints cronológicos por contato com canal/source/UTM resolvido — substitui UTM solto na URL com modelo persistido que sobrevive ao primeiro click e permite multi-touch attribution futura.

Por que existe (negócio)

Sem attribution dedicada:

  • UTM da URL fica em crm_contact.notes ou perdido.
  • Sem histórico de touchpoints (lead viu Instagram → email → ad antes de comprar).
  • Cross-channel ROI impossível.

Sprint 1.A (memória [[brain-observatory-sprint-1a]]) trouxe:

  • Campaign persistida (slug, kind, channel, budget, status).
  • Touchpoint por interação (channel, source_module, position=first/middle/last/conversion, weight).
  • crm_contact.attribution_* populado on-create (resolve UTM → campaign).
  • Bridge organic (Instagram, YouTube) — plugin Hub.5/6 criam campaign quando detecta novo channel/video.

Status atual

  • Sprint 1.A em prod desde 2026-05-13 (commits 09aada4 + a3729da, alembic 0094).

Próxima mudança: Multi-touch models (linear, time-decay) em fase L3 do intelligence; Campaign attributed_revenue auto-sync com billing.


2. Cases de uso reais

Case 1: Lead novo via Meta Ads

Fluxo: Click no ad → URL com ?utm_source=meta&utm_medium=cpc&utm_campaign=mentoria-premium-q2 → submete form → CRM cria contato → attribution.service.attach_attribution(contact, utm) → resolve utm_campaign → match com attribution_campaign(slug=mentoria-premium-q2) (criado pelo plugin meta_ads via attribution_bridge) → crm_contact.attribution_campaign_id populado + attribution_first_touch_at=now.

Case 2: Lead via YouTube orgânico

Fluxo: Click em link na descrição de vídeo → URL sem UTM → attach_attribution falha matching → crm_contact.attribution_first_source='direct'. Plugin Hub.6 já criou attribution_campaign(kind=organic_youtube, external_id=channel_id) mas resolve UTM não match. Felipe pode editar manualmente depois.

Case 3: Multi-touch (futuro)

Beat task percorre contact_touchpoint ordenado por occurred_at, atribui weight (linear: 1/N; time-decay: exponencial decreasing) → atualiza attribution_first_source/attribution_last_source + Conselho lê via read_attribution(contact_id).


3. Oportunidades de negócio

  • Multi-touch attribution as a service: modelar diferentes attribution models (linear, time-decay, U-shaped, position-based) — premium feature.
  • Campaign ROI dashboard: cruzar attributed_revenue_brl com spend de Meta Ads/Google Ads → ROAS por campanha.
  • Cross-channel funnel: "lead viu IG → email → ad antes de comprar" — visualização única.

Riscos: UTM matching é best-effort; sem match = attribution_first_source='direct' (perde info).


4. Arquitetura interna

Arquivos

ArquivoPropósito
models.py2 tabelas
routes.py5 endpoints
service.pyresolve_utm_to_campaign, attach_attribution, append_touchpoint
schemas.pyPydantic

Tasks

Sem Celery. attach_attribution é síncrono on-create contact (chamado por CRM webhook).


5. Tabelas (2)

Naming exception: o nome da tabela é campaign (sem prefixo attribution_), exceção histórica à convenção <modulo>_<tabela> do CLAUDE.md. Mantida assim porque crm_contact.attribution_campaign_id referencia ela e renomear exigiria migração extensa. Quando este doc menciona "attribution_campaign", o nome real no schema é campaign.

campaign

Coluna chaveNotas
tenant_id
slugUNIQUE per tenant
name
kindad_paid / organic_youtube / organic_instagram / newsletter / affiliate / referral / event / webinar / partnership / direct / other
channel
started_at / ended_at
budget_brl
target_audience_md
utm_trackingJSONB {source, medium, campaign, content?, term?}
providerNULL=manual ou ga4 / youtube / meta_ads / etc.
external_idID do provider (ex: campaign_id Meta)
metadata_JSONB livre
attributed_revenue_brlDECIMAL — calculado periodicamente
attributed_contacts_countINT
statusactive / paused / concluded / archived
notes_md

contact_touchpoint

Coluna chaveNotas
tenant_id
contact_idFK lógica → crm_contact
campaign_idNullable
channel
source_modulecrm / email / links / instagram / whatsapp / etc.
event_idFK lógica → tracking_event
occurred_at
positionfirst / middle / last / conversion
weightDECIMAL(5,4) — 1.0 first touch (default)
propertiesJSONB {utm_*}

Relacionamentos cross-módulo

DireçãoOutro móduloComo
↘ Escrevecrm_contactattribution_campaign_id, attribution_first_touch_at, attribution_last_touch_at, attribution_first_source, attribution_first_medium, attribution_first_campaign
↗ Lêmarketing (plugins)Plugins criam campaigns via attribution_bridge.sync_attribution_campaigns
↗ Lêtrackingevent_id em touchpoint

6. API / Endpoints (5)

MétodoRotaO que faz
GET/api/attribution/campaignsLista (filter status), paginação
POST/api/attribution/campaignsCriar manual
PATCH/api/attribution/campaigns/{id}Editar (name, budget, status)
DELETE/api/attribution/campaigns/{id}Arquivar (não deleta touchpoints)
GET/api/attribution/contacts/{contact_id}/touchpointsHistórico cronológico

7. Service principais

  • resolve_utm_to_campaign(utm) — heurística por score: utm_campaign=100, source=10, medium=5. Pick highest.
  • attach_attribution(contact, utm) — on-create CRM: resolve UTM → campaign, cria first touchpoint, popula crm_contact.attribution_*. Idempotente.
  • append_touchpoint(contact_id, channel, source_module, ...) — registra subsequent touches sem mexer first_*.

8. Configuração

Sem env vars específicas. UTM resolution on-create (não retroativo).


9. Operações + Troubleshooting

Sintoma: Lead com attribution_first_source='direct' mesmo vindo com UTM

Causa: UTM não bateu com nenhuma attribution_campaign (matching score 0). Fix: Felipe edita manualmente crm_contact.attribution_* ou cria campaign nova com utm_tracking matching.


10. Limitações e débitos técnicos

#Item
1UTM matching best-effort — sem match = source=direct
2Sem multi-touch model implementado — só first/last touch
3attributed_revenue_brl calculado fora
4Sem FK formalcontact_id UUID solto
5Deletion não retroativa

11. Histórico

  • 2026-05-13 (commits 09aada4 + a3729da, alembic 0094) — Sprint 1.A deployada. Memória [[brain-observatory-sprint-1a]].