//Receita

Extrair datas sem errar a conta

Extrair uma data é leitura; comparar datas é aritmética. A receita separa as duas e resolve o modo de falha declarado da versão.

Um contrato diz “vigência a partir de 1º de janeiro de 2025, com término em 31 de dezembro de 2027”. Um e-mail diz “devolve o formulário assinado até 14 de agosto”. Outro diz “vamos marcar a revisão para quinta que vem”. Você precisa dos três como data de verdade, no banco, para ordenar, comparar e disparar lembrete.

É aqui que muita integração quebra, e por um motivo declarado.

Por que a solução ingênua falha

Pedir a data ao modelo e usar o que voltar é o caminho natural — e é exatamente o que a documentação oficial diz para não fazer. Comparação de data e hora está na lista dos nove modos de falha do jev-1.13: o modelo lê datas como texto, não como quantidade ordenada. Dizer qual vem primeiro, quanto tempo separa duas ou se uma cai dentro de uma janela é pouco confiável, e piora com formatos misturados, referências relativas e fronteiras de trimestre ou competência.

A segunda tentativa é uma expressão regular. Ela resolve “14/08/2026” e falha em “quinta que vem”, “no dia 14 do mês que vem”, “até o fim do próximo trimestre” — que é como as pessoas escrevem.

A terceira é pedir a data a um LLM generativo, e aí você troca o problema de leitura por um problema de confiança: o modelo pode devolver uma data plausível que o documento não diz.

O desenho das perguntas

A divisão da receita é a lição inteira: extração é julgamento, aritmética não é. O modelo lê as partes que o texto nomeia; o código monta a data.

São sete perguntas Choice numa chamada. A primeira descobre a forma da data; as outras seis leem as peças:

from typesafe_sdk import Choice

MESES = ["janeiro", "fevereiro", "março", "abril", "maio", "junho",
         "julho", "agosto", "setembro", "outubro", "novembro", "dezembro"]
DIAS_DA_SEMANA = ["segunda", "terça", "quarta", "quinta", "sexta", "sábado", "domingo"]
ausente = "O documento não informa isso, ou não é uma data deste tipo."

def perguntas_de_data(papel: str) -> dict:
    return {
        "tipo": Choice(
            instructions=(
                f"Como {papel} está escrita? 'absoluta' = data de calendário que nomeia o mês; "
                "'relativa' = dada em relação a hoje (hoje, amanhã, depois de amanhã ou um dia "
                "da semana nomeado); 'nenhuma' = o documento não informa esta data."
            ),
            criteria={"absoluta": None, "relativa": None, "nenhuma": None},
        ),
        "mes": Choice(
            instructions=f"Se {papel} é uma data absoluta, de que mês é?",
            criteria={m: None for m in MESES} | {"nao_informado": ausente},
        ),
        "dia": Choice(
            instructions=f"Se {papel} é uma data absoluta, que dia do mês (1 a 31)?",
            criteria={str(d): None for d in range(1, 32)} | {"nao_informado": ausente},
        ),
        "ano": Choice(
            instructions=(
                f"Se {papel} é uma data absoluta, de que ano? Escolha 'nao_informado' se o "
                "documento não diz o ano, ou 'fora_da_lista' se diz um ano fora da lista."
            ),
            criteria={str(a): None for a in range(1900, 2051)}
            | {"fora_da_lista": "Um ano é informado, mas está fora da lista.",
               "nao_informado": "Nenhum ano é informado para esta data."},
        ),
        "ancora": Choice(
            instructions=f"Se {papel} é relativa a hoje, qual é o dia?",
            criteria={"hoje": None, "amanha": None, "depois_de_amanha": None,
                      "dia_da_semana": None, "nao_informado": ausente},
        ),
        "dia_da_semana": Choice(
            instructions=f"Se {papel} nomeia um dia da semana, qual?",
            criteria={d: None for d in DIAS_DA_SEMANA} | {"nao_informado": ausente},
        ),
        "semana": Choice(
            instructions=(
                f"Se {papel} nomeia um dia da semana, de que semana? 'proxima' para 'quinta que "
                "vem'; 'atual' para 'esta quinta'; 'nenhuma' para um dia solto, sem qualificador."
            ),
            criteria={"atual": None, "proxima": None, "nenhuma": None},
        ),
    }

Duas coisas merecem destaque. Cada parte de uma data é um conjunto fechado — doze meses, trinta e um dias possíveis, uma faixa de anos —, e é isso que transforma extração em escolha entre opções enumeradas em vez de texto livre. E cada pergunta tem escape: nao_informado e fora_da_lista existem para o modelo poder dizer que o texto não diz, em vez de chutar. Como escrever esses critérios está em critérios que separam.

O código faz o resto: preenche o ano quando o texto não informa, resolve qual quinta-feira é “quinta que vem” pela convenção que você escolheu, e reporta a menor confiança entre as partes usadas.

O que o experimento oficial mediu

O cookbook roda a função sobre quatro documentos curtos, com “hoje” fixado em 30 de julho de 2026 (uma quinta-feira) para que as datas relativas sejam reproduzíveis, e limiar de revisão em 0,60. Os números publicados pela TypeSafe, com o jev-1.12:

O que foi pedidoResultadoConfiançaAção
Data de início do contrato2025-01-010,97automática
Data de término do contrato2027-12-310,91automática
Prazo para devolver o formulário (sem ano no texto)2026-08-140,95automática
Data de uma reunião que o formulário nunca mencionanenhuma0,46revisão
Data em que a pesquisa fecha (“hoje”)2026-07-300,94automática
Data da revisão de design (“quinta que vem”)2026-08-060,92automática

Cinco aceitas automaticamente, uma para revisão. As duas linhas mais interessantes são a do prazo sem ano — o modelo respondeu “não informado” e o código preencheu 2026 — e a da data inexistente: o tipo voltou “absoluta” sem mês para acompanhar, o código não conseguiu montar data nenhuma, e a confiança de 0,46 sinalizou o caso para uma pessoa. Nenhuma data inventada.

O que muda em português

Aqui a adaptação é real e vale mais que uma tradução. Três pontos:

Formato. “14/08” é dia/mês no Brasil e mês/dia nos Estados Unidos. Como as opções são enumeradas, você evita a ambiguidade perguntando mês e dia separadamente em vez de pedir a data formatada — o que já é o desenho da receita.

Vocabulário nosso. “Semana que vem”, “quinze dias” (que costuma significar duas semanas), “no dia 10 do mês que vem”, “até o fim do expediente”. Vale enumerar as âncoras que a sua base realmente usa, com descrição explícita.

Dias úteis. Prazo brasileiro conta dia útil com frequência (“cinco dias úteis para estorno”), e feriado varia por município. Isso é calendário, ou seja, código — e mais um motivo para o modelo não fazer a conta.

Como a TypeSafe declara que o inglês é a língua principal de treino, teste as sete perguntas com os seus documentos antes de baixar o limiar de revisão.

Onde isso quebra

  • Aritmética de data continua fora. A receita extrai; ordenar, medir distância e checar janela é código. O modo de falha está descrito em limites do jev-1.13.
  • Lista longa de opções. O ano como conjunto de 1900 a 2050 é grande. O próprio cookbook sugere, se isso incomodar, extrair os números com cara de ano antes e oferecer só esses ao modelo — o que é a receita de extrair valores.
  • Data em documento grande. Contrato inteiro no state infla a entrada e derruba acurácia; mande a cláusula, não o contrato.
  • Duas datas do mesmo tipo. “A partir de” e “até” no mesmo parágrafo exigem uma rodada de perguntas por papel, com o papel nomeado na instrução — é assim que o experimento pega início e fim do contrato.

O índice das receitas está em receitas.

Perguntas frequentes

Por que não pedir a data direto ao modelo?

Porque a documentação lista comparação de datas entre os modos de falha do jev-1.13: o modelo lê datas como texto, não como quantidade ordenada. Pedir as partes é leitura, que ele faz bem; montar e comparar é aritmética, que pertence ao código.

Quantas perguntas a receita usa?

Sete perguntas Choice em uma única chamada: o tipo da data (absoluta, relativa ou inexistente), mês, dia, ano, âncora relativa, dia da semana e qual semana. O código lê só as partes que o tipo exige.

Como a receita lida com data que o documento não menciona?

Com escapes explícitos. O tipo tem a opção 'nenhuma' e as partes têm a opção 'não informado', então o modelo pode dizer que o texto não diz, em vez de adivinhar. No experimento, a data inexistente voltou vazia com confiança 0,46 e foi para revisão.

E se o ano não estiver escrito?

O modelo responde 'não informado' e o código preenche: usa o ano corrente e avança para o próximo quando a data já passou há mais de um mês. Foi assim que o prazo sem ano resolveu para 2026-08-14 no experimento.

Qual confiança a data recebe?

A menor entre as partes que entraram na montagem. Assim, uma leitura fraca em qualquer parte derruba a confiança da data inteira, e o limiar de revisão do cookbook (0,60) pega o conjunto.

Quer dominar decisões com IA em português? O curso da comunidade está em pré-venda.

Garantir pré-venda por R$ 499,00

Pré-venda: R$ 499,00 · Após o lançamento: R$ 799,00