Pular para conteúdo

Estrutura base da coleta (Harvest)

Uma coleta eh o conjunto de respostas de um respondente a um questionario. Ela chega na plataforma como um arquivo JSON gravado no bucket S3.

O arquivo pode conter um objeto (uma coleta) ou um array de objetos (varias coletas no mesmo arquivo). Os dois formatos sao aceitos.

{
  "uuid": "<UUID da coleta. OBRIGATORIO. Eh a chave de deduplicacao: reenviar um JSON com o mesmo uuid ATUALIZA a coleta existente em vez de criar uma nova>",
  "questionnaire": <id do questionario. OBRIGATORIO. Sem esta chave a coleta eh rejeitada. O questionario precisa existir e nao estar deletado>,
  "release": <numero do release do questionario usado na aplicacao. Define de qual versao do questionario sao os ids de questao e de opcao usados em `values`. Veja a secao "Relacao com o release">,
  "created": "<data/hora de INICIO da entrevista, no formato ISO 8601. OBRIGATORIO>",
  "updated": "<data/hora de FIM da entrevista, no formato ISO 8601. OBRIGATORIO>",
  "pin": "<pin do dispositivo que fez a coleta. OBRIGATORIO (a chave precisa sempre existir no JSON, mesmo que o valor seja null). null em coletas online (feitas pelo navegador, sem app). Se informado, o dispositivo precisa existir, senao a coleta eh rejeitada>",
  "user": <id do entrevistador que aplicou o questionario. null em coletas online>,
  "user_id": <id do usuario da plataforma que respondeu. Usado em coletas online. null em coletas de campo>,
  "interviewer": <id do entrevistador. Alternativa a chave `user`, com o mesmo efeito. Veja a secao "user, user_id e interviewer">,
  "finished": <true se a entrevista foi concluida ate o fim, false se foi interrompida>,
  "valid": <true se a coleta eh considerada valida>,
  "denied": <true se o respondente recusou a entrevista, false caso contrario>,
  "lat": <latitude decimal capturada no momento da coleta. 0 quando nao houve captura de GPS>,
  "lng": <longitude decimal capturada no momento da coleta. 0 quando nao houve captura de GPS>,
  "accuracy": <precisao do GPS em metros. Apenas informativo, NAO eh gravado no banco>,
  "id": <id da coleta no banco local do dispositivo. IGNORADO pelo servidor>,
  "cati_contact_uuid": "<uuid do contato no modulo CATI, quando a coleta veio de uma ligacao telefonica. null nos demais casos>",
  "cati_phone": "<telefone usado na ligacao CATI. null nos demais casos>",
  "pre_fill_data_item_id": <id do item de pre-preenchimento que originou a coleta. null quando a coleta nao veio de uma base pre-carregada>,
  "whatsapp": { "recipient": "<telefone do respondente no WhatsApp, somente digitos>" },
  "url_variables": [<array de variaveis de URL capturadas em coletas online, descrito abaixo>],
  "values": [<array com as respostas da coleta, descrito abaixo>]
}

Regras:

  • uuid, questionnaire, created e updated sao obrigatorios. A ausencia de qualquer um deles faz a coleta inteira ser rejeitada.
  • pin tambem eh obrigatorio, mas de um jeito diferente dos quatro acima: o processamento acessa essa chave diretamente (sem tratar ausencia), entao omiti-la nao gera uma rejeicao limpa da coleta — o processamento do arquivo inteiro quebra com erro (KeyError: 'pin') e o arquivo eh marcado como FAILED no bucket. Em coletas sem dispositivo (online), envie "pin": null — nunca omita a chave.
  • created eh a data de inicio e updated a data de fim da entrevista. updated nunca deve ser anterior a created.
  • Toda chave cujo nome coincida com uma coluna da coleta eh gravada automaticamente. Chaves desconhecidas sao ignoradas silenciosamente — nao geram erro, mas tambem nao sao armazenadas em lugar nenhum.
  • Ao gerar o JSON para atualizar uma coleta existente, preserve todos os campos do JSON original, mesmo os desconhecidos, e altere apenas o que precisa mudar.
  • Nao existe campo surplus no JSON. Ele eh calculado pelo servidor (marca coletas excedentes de questionarios em modo teste).
  • Nao existe campo de duracao. A duracao eh derivada de updated - created.

user, user_id e interviewer

Sao tres chaves distintas e frequentemente confundidas:

Chave O que identifica Quando usar
user o entrevistador que aplicou o questionario coletas de campo. No processamento esta chave eh renomeada para interviewer
interviewer o entrevistador equivalente a user. Pode ser usada diretamente
user_id o usuario da plataforma que respondeu coletas online, em que o proprio respondente esta logado

Regras que decorrem disso:

  • Em coleta de campo: preencha user (ou interviewer) e pin; deixe user_id como null.
  • Em coleta online: preencha user_id; deixe pin e user/interviewer como null.
  • Nunca envie user e interviewer ao mesmo tempo com valores diferentes.

Estrutura de resposta (item de values)

Cada item do array values eh a resposta de uma questao.

{
  "question": <id da questao. OBRIGATORIO. A questao precisa existir, senao a coleta inteira eh rejeitada>,
  "datadoc": {<dicionario com a resposta em si, descrito abaixo>},
  "harvest": "<id ou uuid da coleta no banco local do dispositivo. IGNORADO pelo servidor>",
  "id": <id do value no banco local do dispositivo. IGNORADO pelo servidor>
}

Regras:

  • Existe no maximo um item por questao. Nunca envie dois objetos com o mesmo question no mesmo array values — o segundo sobrescreve o primeiro.
  • Questoes nao respondidas simplesmente nao aparecem no array values. Nao envie um item com datadoc vazio para representar ausencia de resposta; use __blank__ (veja abaixo) quando quiser registrar explicitamente que a questao foi deixada em branco.
  • Uma questao matriz nao gera um item proprio em values. Cada linha da matriz eh uma questao filha e aparece como um item independente, no formato de questao de escolha unica.
  • Respostas dentro de loops nao sao importadas por este formato.

Estrutura do datadoc

O datadoc eh um dicionario onde:

  • as chaves sao ids de opcao (id da opcao no JSON do questionario), sempre como string;
  • os valores sao o que foi respondido naquela opcao.

Alem das chaves numericas, existem chaves especiais com duplo underline, descritas mais abaixo.

{
  "<id da opcao>": "<valor respondido>",
  "<id de outra opcao>": "<valor respondido>"
}

O que vai no valor depende do tipo da opcao:

class_name da opcao O que vai no valor
Option o campo value da opcao (o codigo numerico dela, como string). Ex.: "3"
OtherOption o texto digitado pelo respondente no campo "Outro". Ex.: "Vila Rica"
InputOption o texto digitado naquele campo da questao aberta. Ex.: "Sofia Reguel"

Chaves especiais

Chave Significado Valor
ordered ordem de selecao das opcoes, usada em questoes de escolha multipla com widget_ordered_selection = true array com os ids das opcoes, na ordem em que foram selecionadas
__blank__ a questao foi deixada em branco o blank_value configurado no questionario. Ex.: "#", "NSNR", "99"
__dkda__ resposta "Nao sabe / Nao respondeu" (NSNR), disponivel em questoes com option_dkda = true o literal "__dkda__"
__none__ opcao de exclusao ("Nenhuma das anteriores"), disponivel em questoes com option_none = true o option_none_value da questao, ou o id da opcao. Ex.: "472", "__none__"
__picture__ resposta de questao de foto data URI em base64 no envio; vira URL do S3 depois de processada
__sign__ resposta de questao de assinatura data URI em base64 no envio; vira URL do S3 depois de processada
__audio__ resposta de questao de audio data URI em base64 no envio; vira URL do S3 depois de processada
value formato generico, legado evite usar. Se o valor for vazio, null ou 0, a chave eh descartada no processamento

Regras:

  • As chaves numericas sao ids de opcao, nunca ids de questao. O id da questao aparece so no campo question do item de values.
  • As chaves sao sempre strings, mesmo sendo numericas: "12109244", nao 12109244.
  • Os valores tambem sao strings, inclusive os codigos numericos das opcoes: "3", nao 3.
  • Os ids de opcao usados precisam existir no release informado na coleta. Veja a secao "Relacao com o release".
  • __blank__, __dkda__ e __none__ sao mutuamente exclusivas entre si e com as chaves de opcao: quando uma delas esta presente, ela eh a resposta inteira e o datadoc nao tem mais nada (a excecao eh ordered).
  • Envie no maximo uma chave de midia (__sign__, __picture__ ou __audio__) por datadoc. Se houver mais de uma, apenas a primeira na ordem __sign__ -> __picture__ -> __audio__ eh processada; as demais sao gravadas como base64 cru.

Formato por tipo de questao

Escolha unica (ChoiceQuestion)

Uma unica chave: o id da opcao escolhida, apontando para o value dessa opcao.

{ "12109244": "3" }

Quando a opcao escolhida eh uma OtherOption ("Outro, qual?"), o valor eh o texto digitado em vez do codigo:

{ "137495": "Vila Rica" }

Escolha multipla (MultipleChoiceQuestion)

Uma chave por opcao marcada. As opcoes nao marcadas simplesmente nao aparecem.

{
  "12127421": "3",
  "12127422": "4"
}

Se a questao usa selecao ordenada (widget_ordered_selection = true), acrescente a chave ordered com os ids na ordem em que foram escolhidos:

{
  "12127421": "3",
  "12127422": "4",
  "ordered": ["12127421", "12127422"]
}

A posicao de cada id dentro de ordered eh o que define a prioridade da selecao (primeiro elemento = primeira escolha).

Questao aberta (OpenQuestion)

Uma questao aberta pode ter varios campos, cada um sendo uma InputOption. Cada chave eh o id do campo e o valor eh o texto digitado nele:

{
  "103006": "Sofia Reguel",
  "103007": "Rua Herminio Dagnone 196",
  "103008": "Vl Nova",
  "103009": "47 996791313"
}

Se a questao aberta tem um unico campo, o datadoc tem uma unica chave:

{ "260383": "Lidiane Souza" }

Escala (ScaleQuestion)

Mesmo formato da escolha unica: id da opcao da escala apontando para o valor selecionado.

{ "1332159": "6" }

Matriz (MatrixQuestion)

A questao matriz nao tem datadoc proprio. Cada linha da matriz eh uma questao filha (ChoiceQuestion com matrix_question_id preenchido) e aparece como um item separado em values, no formato de escolha unica. Os ids de opcao usados sao os da questao matriz, que sao herdados pelas filhas.

Foto, assinatura e audio (PictureQuestion, SignatureQuestion, AudioQuestion)

No JSON enviado ao bucket, o conteudo vai como data URI em base64:

{ "__picture__": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQ..." }

Extensoes reconhecidas: png, jpg, jpeg, mp3. O prefixo do data URI (os 22 primeiros caracteres) eh usado para identificar a extensao, entao ele precisa estar presente e bem formado.

Ao analisar uma coleta ja processada, espere sempre uma URL. Ao gerar uma coleta nova, envie o base64.

Variaveis de URL

Coletas online podem carregar parametros de query string capturados na URL de acesso ao formulario:

"url_variables": [
  { "name": "utm_source", "value": "Instagram_Feed" },
  { "name": "utm_medium", "value": "paid" },
  { "name": "sname",      "value": "6TLO0enjtz_6OPXQslWfp56eQl4" }
]

Regras:

  • name e value sao ambos strings e ambos obrigatorios.
  • As variaveis aceitas sao as declaradas no array url_variables do JSON do questionario.
  • O array pode ser omitido ou vir vazio em coletas de campo.

Relacao com o release

O JSON de coleta eh puramente numerico: ele so carrega ids de questao e ids de opcao. Quem da significado a esses ids eh o release, que eh o snapshot da estrutura do questionario no momento em que ele foi publicado.

O vinculo eh feito pelo par questionnaire + release da coleta. Ele aponta para um snapshot que contem a lista completa de questoes e opcoes vigentes na epoca, e eh esse snapshot que traduz cada id de opcao em rotulo e em coluna de exportacao.

Isso existe para que uma coleta antiga continue sendo lida com a estrutura da epoca em que foi respondida, mesmo que o questionario tenha sido alterado depois.

Regras:

  • O release informado precisa existir para aquele questionario.
  • Todos os ids de questao e de opcao usados em values devem pertencer ao release informado. Usar um id de opcao que so existe em um release mais novo produz uma coleta que nao consegue ser exportada corretamente.
  • Ao gerar um JSON de coleta, sempre parta do JSON do release correspondente para obter os ids de opcao e os value de cada opcao. Nao invente ids.
  • Ao analisar um JSON de coleta, o release eh a primeira coisa a olhar: sem ele nao ha como saber a que questao/opcao cada id se refere.

Exemplos completos

Coleta online, com uma questao aberta

{
  "uuid": "4c6ff122-5d5f-47f6-93ab-a5ce4124e766",
  "questionnaire": 237687,
  "release": 1,
  "created": "2026-08-11T14:20:20.751Z",
  "updated": "2026-08-11T14:28:07.699Z",
  "pin": null,
  "interviewer": null,
  "user_id": 2762,
  "finished": true,
  "valid": true,
  "denied": false,
  "lat": -19.7521501,
  "lng": -43.9341315,
  "accuracy": 5100,
  "values": [
    {
      "question": 237689,
      "datadoc": { "74346": "jonathan" }
    }
  ]
}

Coleta de campo, com varios tipos de questao

{
  "uuid": "b51110b1-7e89-4fb7-8457-44c0b68f69a6",
  "questionnaire": 1325786,
  "release": 1,
  "created": "2026-08-11T13:55:13.676Z",
  "updated": "2026-08-11T13:58:43.724Z",
  "pin": "M0FMWK",
  "user": 23506,
  "user_id": null,
  "finished": true,
  "valid": true,
  "denied": false,
  "lat": -30.023284,
  "lng": -51.217722,
  "values": [
    {
      "question": 12109240,
      "datadoc": { "12109244": "3" }
    },
    {
      "question": 12127420,
      "datadoc": {
        "12127421": "3",
        "12127422": "4",
        "ordered": ["12127421", "12127422"]
      }
    },
    {
      "question": 12109290,
      "datadoc": { "137495": "Vila Rica" }
    },
    {
      "question": 12109300,
      "datadoc": { "__blank__": "#" }
    },
    {
      "question": 12109310,
      "datadoc": { "__dkda__": "__dkda__" }
    },
    {
      "question": 12109320,
      "datadoc": { "__none__": "472" }
    }
  ]
}

Nome do arquivo no bucket

O nome do arquivo nao identifica a coleta (isso vem do conteudo), mas segue um padrao fixo e precisa terminar em harvest.json ou harvest.zip para ser processado:

<data>-<uuid da coleta>-<pin do dispositivo>-harvest.json

Exemplo: 2026-08-11T11.16.46-b51110b1-7e89-4fb7-8457-44c0b68f69a6-M0FMWK-harvest.json

Regras:

  • Quando nao ha dispositivo (coleta online), o literal null (minusculo) ocupa a posicao do pin: ...-4c6ff122-5d5f-47f6-93ab-a5ce4124e766-null-harvest.json.
  • No caso do .zip, o JSON dentro do arquivo compactado deve ter **o mesmo nome da key, trocando .zip por .json**.