O formato
Uma conjunção de grupos; dentro de um grupo, uma disjunção de condições.(A OU B) E C, e dois níveis são a gramática inteira. Aninhamento arbitrário é
uma questão de precedência, um parser e um formulário que ninguém consegue
desenhar; qualquer-um E qualquer-um é o que um construtor de audiência de fato
oferece, e este diz em voz alta o que não consegue expressar em vez de suportar
pela metade.
No máximo 8 grupos de 12 condições. Isso é uma audiência grande e uma query que
o Postgres planeja sem notar — o limite existe para que uma definição não possa
ficar arbitrariamente cara de colar.
Um grupo vazio é recusado, nunca lido como “sempre verdadeiro”: um construtor
que perdesse a última condição viraria silenciosamente “todo mundo”.
Quatro fontes
Quais operadores se aplicam é decidido pelo campo, e uma incompatibilidade é
recusada:
created_at contains "@" não é uma query com resposta vazia, é uma
pergunta que ninguém quis fazer. Campos de texto aceitam eq/neq/contains/
not_contains/starts_with/ends_with/in/not_in/is_set/is_not_set;
booleanos aceitam is_true/is_false; timestamps aceitam before/after/
within_days/not_within_days; eventos aceitam occurred/not_occurred;
métricas aceitam at_least/at_most.
Um operando que a condição não lê é recusado em vez de ignorado. Um days numa
condição onde dias não significam nada seria uma janela que você definiu, o motor
descartou, e você acredita ter.
A janela de uma condição de evento é obrigatória (1 a 730 dias). Não existe
occurred sem limite, porque um segmento que varre todo evento já armazenado fica
mais lento a cada mês em que é deixado em paz. Se você quer dizer “alguma vez”,
diga dois anos e assuma isso.
Um timestamp não preenchido não casa com nenhum dos quatro operadores de tempo
— not_within_days inclusive. Um valor ausente não responde pergunta nenhuma sobre
si mesmo; alcance esses contatos com is_not_set.
Filtrando por uma propriedade do evento
Uma condição de evento pode olhar uma chave do que otrack() mandou:
checkout.completed.pro ao lado de checkout.completed.free — o que transforma o
namespace num produto cartesiano e leva junto o agrupamento em que o explorer é
construído.
Quatro regras, e cada uma existe por um motivo que aparece depois:
- Só em
occurred. “Não aconteceu com plan=pro” tem duas leituras — nunca aconteceu, ou aconteceu com outro plano — e o motor recusa em vez de escolher. Para a primeira, usenot_occurredno evento inteiro. - Uma chave por condição. Duas seriam um E, e um grupo de condições já é como este motor escreve E.
- A comparação é textual. Uma propriedade é comparada como o texto que ela
vira:
4é"4",trueé"true". A coluna não tem schema, e qualquer outra leitura precisaria de um tipo declarado por chave. Os operadores são os de texto, menosin/not_in— que sobre uma propriedade seriam um OU, e um OU é uma segunda condição no mesmo grupo. - Ausente e
nullsão o mesmo estado que vazio. Entãois_not_seté como você pede os eventos que não carregam a chave, eneqnão casa com eles por acidente.
count continua valendo junto: “fez checkout com plan = pro ao menos 2 vezes”
é uma condição só.
Uma propriedade de evento não é um atributo do contato. Se você escrever
properties.plan como attribute, a recusa aponta para cá.
O que ele recusa, e por que recusar é melhor que aproximar
Duas condições são perfeitamente razoáveis de querer, e este motor não vai formulá-las. As duas se nomeiam no erro, então você encontra uma frase em vez de uma audiência vazia. Um atributo customizado do contato.attributes.plan_tier não tem nada por
trás — as colunas do contato são o perfil e a atribuição de primeiro toque, e não
há armazém de atributos contra o qual casar. O único jeito de honrar isso seria
casar algo quase certo. Um segmento construído sobre uma aproximação não é um
erro menor que uma mensagem de erro: é uma audiência que recebe e-mail, ou uma
funcionalidade que sai para o décimo errado dos seus usuários, e ninguém descobre
isso lendo a definição.
Um entitlement. Se um cliente detém uma funcionalidade é resolvido pelo
motor de entitlements — overrides que substituem em
vez de maximizar, uma expiração, uma carência de dunning, e um vocabulário de
status cujo membro desconhecido responde honestamente “não dá para dizer”.
Reescrever isso em SQL seria uma segunda cópia da única função que é mantida
sozinha de propósito, e a cópia estaria errada justamente nos casos que mais
importam. O que um segmento pode mirar é o plano, que é uma linha:
plan.key e plan.subscription_status são espelhos do seu gateway, não resoluções
de coisa alguma.
Uma terceira recusa é mais estreita e vale saber: uma condição metric num
ambiente test. O medidor de atividade nunca registra test, então a condição só
poderia casar com ninguém — e uma audiência vazia por razão estrutural é idêntica
a uma audiência vazia porque ainda não aconteceu nada.
Dois avaliadores, um significado
A mesma definição é compilada duas vezes: em SQL sobre os contatos de um ambiente (“quem está aqui”), e numa avaliação em processo de um contato (“essa pessoa está aqui, agora”). Uma campanha resolvendo destinatários quer o primeiro; uma flag decidindo o que mandar para um navegador noboot, ou uma pesquisa perguntando se
alguém acabou de entrar, quer o segundo.
Duas implementações de um significado é exatamente o formato que se descola, então
cada campo carrega as duas metades num só lugar e um teste conduz contatos reais
pelos dois caminhos e recusa uma discordância. Vale saber que isso existe, porque é
a razão de você poder confiar no preview: a contagem que você viu é produzida pelas
mesmas regras que a flag vai aplicar um carregamento de página depois.
Pertencimento, entrada e saída
Quem está num segmento é escrito, não recalculado a cada leitura — porque o que os módulos acima de fato assinam é a entrada. “Alguém entrou em trial-acaba-em-3-dias” é uma diferença entre duas avaliações, e uma diferença não tem onde morar a menos que a avaliação anterior tenha sido registrada. Duas coisas mantêm isso vivo:- Um fato chega e os contatos sobre os quais ele fala são re-decididos em segundos. É o caminho que carrega tudo causado por alguém ter feito algo.
- Uma varredura noturna percorre cada definição viva e recalcula. É a única coisa capaz de notar uma condição que virou verdadeira porque o tempo passou — “criado há mais de 30 dias”, “não visto há três semanas” viram verdade sem nada ser publicado em lugar nenhum, porque nada aconteceu.
segment.entered e sair publica
segment.left. Os dois são entregáveis como webhook, e o payload leva os ids e o
nome do segmento — nunca a definição.
O primeiro cálculo de uma definição não anuncia nada. Escrever uma audiência
pela primeira vez é um backfill: ninguém mudou, a audiência mudou, e um webhook por
contato existente por um ato que você fez no painel três segundos atrás não é um
fato sobre ninguém. Editar a definição devolve o segmento a esse estado, pelo mesmo
motivo — re-mirar uma audiência não é cem pessoas entrando nela.
definition_version sobe quando a definição muda e nunca num rename, então
qualquer coisa segurando uma audiência compilada descobre que está velha comparando
um inteiro.
left_at é um timestamp e não uma exclusão, porque sair também é algo sobre o qual
você vai querer agir: uma audiência de reconquista é gente que saiu de “ativo nos
últimos 30 dias”.
Faça o preview antes de mirar qualquer coisa
Permissões
Ler um segmento écustomers:read — o mesmo assento que lista contatos, porque um
preview é ler contatos. Criar, editar e arquivar é segments:manage, que owner e
admin têm por padrão.
A separação é deliberada. Uma audiência é configuração de produto que todo outro
módulo herda; poder olhar para uma não é o mesmo ato que poder mover aquilo para
onde uma flag, uma pesquisa e uma nota de versão apontam ao mesmo tempo.