Skip to main content
Cinco tabelas, todas só-append. Nada é jamais atualizado. É isso que torna delivery receipts concorrentes seguros e permite que uma mensagem re-roteada mantenha as duas tentativas.

Como se juntam

A view cdr_pnl faz left join de submit e final, então registros sem desfecho ainda aparecem como PENDING. Exposição em voo continua visível em vez de ser excluída em silêncio.
Todo leitor precisa pegar a linha mais antiga de cdr_final para uma mensagem. É por isso que receipts de progresso vivem na própria tabela: um ACCEPTD escrito em cdr_final teria precedência sobre o DELIVRD que veio depois, em toda listagem, todo gráfico e no P&L.

cdr_submit

A tabela portadora. A parte 1 é o único registro por mensagem do que foi cobrado.

Identidade e formato

O dinheiro, os dois lados

Todo dinheiro é bigint de décimos-milésimos. Divida por 10 000 para exibir; nunca guarde um float.
NULL money é desconhecido, nunca zero. Uma tarifa que não pôde ser determinada continua distinguível de uma mensagem genuinamente gratuita.Qualquer query que faça coalesce disso para zero reporta um fornecedor sem preço como pura margem. margin_status existe para tornar esse estado explícito em vez de inferido.
O dinheiro vive inteiramente em part_no = 1. Segmentos posteriores são linhas de entrega sem dinheiro. Escrever valores em cada segmento multiplicaria os dois lados pela contagem de segmentos. Toda query de dinheiro precisa de WHERE part_no = 1.

Duas moedas, e sem conversão

cost_currency e price_currency são registradas por lado. FireFlo nunca converte entre elas. Uma implantação comprando em uma moeda e vendendo em outra tem uma coluna de margin que não pode ser somada sem fazer a conversão você mesmo.

As três colunas de TLV

NULL significa nada registrou a coluna; {} significa registrado e não havia nenhum. tlvs_sent é NULL para todo fornecedor não-SMPP.

body, e por que geralmente é NULL

Registrado só se smsg.cdr.body é excerpt ou full (V1.31.0). NULL em toda linha caso contrário, e em toda linha escrita antes de a coluna existir. Pesquisado com ILIKE sobre um índice trigram (V1.32.0), sempre combinado com uma faixa de data. Esse índice precisa de pg_trgm; se o usuário de migração não pode criar extensões, a busca ainda funciona, mais devagar.

registered_delivery

O octeto SMPP como o cliente enviou, cru em vez de decodificado. Ele carrega três pedidos independentes e só alguns são atendidos.
Responde uma pergunta que o registro não conseguia responder antes: devíamos um receipt a este cliente?NULL em linhas escritas antes de a coluna existir e em uma entrada sem esse conceito. Isso não é zero. Zero é uma afirmação sobre o cliente, a saber, que ele não pediu nada.

submit_status e vendor_attempt

Desde V1.40.0. Uma linha cdr_submit agora é escrita para toda tentativa contra um fornecedor, não somente aquela que finalmente teve sucesso ou falhou terminalmente — um nack retentável escreve uma também.
NULL é “não registrado”, nunca uma afirmação de sucesso. Linhas escritas antes de V1.40.0, e toda linha de um fornecedor não-SMPP, carregam NULL aqui — leia-o do mesmo jeito que tlvs_sent, não como 0.
Antes desta coluna, uma tempestade de retentativas era visível somente como uma contagem de submits inflada no fornecedor, sem registro do que ele efetivamente disse nas tentativas que falharam — veja Filas e retentativas para o orçamento de retentativas que essas linhas agora tornam legível. (gateway_msg_id, part_no, retry_count, vendor_attempt) é a chave natural a partir de V1.40.0, alargada de (gateway_msg_id, part_no, retry_count) — retry_count sozinho não muda entre retentativas dentro do worker em um fornecedor, então sem vendor_attempt essas linhas colidiriam.

cdr_final e cdr_interim

Desfechos e progresso, separados pela razão acima. Também têm vidas diferentes: dlr_state guarda o valor SMPP cru ao lado do desfecho grosseiro, porque EXPIRED, REJECTD e UNDELIV todos billam como falha mas significam coisas diferentes.

cdr_rejected

Uma linha por recusa, com reason e, desde V1.27.0, o código de status do fornecedor quando o fornecedor foi o que recusou. Uma linha recusada não carrega dinheiro. price, cost e margin são NULL porque o crédito foi devolvido no momento da recusa, o que permite que queries de receita somem essas colunas sem um filtro de desfecho.

usage_daily

Uma linha por conta, por produto, por dia. Diário em vez de mensal para que qualquer período seja um SUM sobre dias, para sempre, sem uma segunda tabela derivada que discorde. Colunas billable de um dia terminado são finais; colunas de entrega são restatadas rodando o rollup de novo, e é por isso que é idempotente.
O purge recusa apagar qualquer dia sem uma linha usage_daily, e recusa a execução inteira em vez de pular. Apagar um dia não sumarizado destrói o registro de consumo permanentemente.

Relacionados

Call records

Para o que cada tabela serve, em prosa.

Tabelas de configuração

A outra metade do schema.