O JSON está válido. O prazo continua inventado.
A anotação diz “Possível lançamento na sexta-feira”. O resumo diz “Lançamento confirmado para sexta-feira”.
As duas frases cabem em um JSON perfeitamente válido. Um esquema pode verificar se date contém uma data. Ele não consegue dizer quem confirmou essa data.
Uma forma simples de tornar essa mudança visível é manter uma tabela revisada dos fatos ao lado do rascunho e comparar os campos de cada afirmação com essa tabela. Vamos apontar uma previsão que virou compromisso, uma pessoa que ganhou uma tarefa extra e uma exportação bem-sucedida confundida com uma importação bem-sucedida. Sem chamada a um modelo, chave de API ou instalação de pacotes.
Comece pelas anotações, não pelo resumo pronto
Estas cinco anotações descrevem o projeto:
- S1: O rascunho da página de destino está pronto. Ainda não há aprovação do cliente.
- S2: Ana deve enviar o rascunho ao cliente na terça-feira, 27 de outubro de 2026.
- S3: A exportação da planilha passou em três verificações por amostragem. A importação no sistema seguinte ainda apresenta erro.
- S4: Possível lançamento na sexta-feira, 30 de outubro. Data proposta, não confirmada.
- S5: A página de ajuda precisa de um responsável. Ninguém foi designado.
Três detalhes precisam de campos próprios. O lançamento tem uma data e um grau de certeza. O responsável pela página de ajuda está explicitamente indefinido. Exportação e importação são etapas diferentes, mesmo quando usam o mesmo arquivo.
Primeiro, uma pessoa confere a tabela de fatos com essas anotações. Cada fato recebe um identificador, a referência da anotação, um assunto, um campo, um valor e um grau de certeza. recorded indica que o valor foi registrado na anotação; não significa que a tarefa terminou. Para a data do lançamento, usamos proposed. Para um responsável ainda não designado, usamos null.
Cada afirmação do rascunho recebe os mesmos campos e uma referência a um fato. Uma pessoa precisa conferir se esses campos representam o que a frase realmente diz. Se outro modelo preencher os campos, esse trabalho também precisa ser revisado. Caso contrário, estaríamos pedindo a um modelo sem revisão que certificasse o resultado de outro.
Dê ao código uma tarefa que ele consegue cumprir
A comparação é propositalmente literal. Ela verifica a referência e compara assunto, campo, valor e grau de certeza. Não reescreve o resumo, não inventa um responsável e não escolhe qual anotação é a mais recente. Um registro malformado gera um erro, em vez de parecer uma verificação bem-sucedida.
Salve o exemplo completo abaixo como demo.mjs e execute com Node.js 20 ou mais recente:
node demo.mjs
O código já inclui as anotações, a tabela de fatos, as afirmações, as verificações e o resultado em português. Os nomes dos campos e os códigos internos permanecem em inglês para que as duas versões usem o mesmo formato de dados.
// Compare afirmações estruturadas com uma tabela de fatos revisada por uma pessoa.
// O código não interpreta a redação nem decide se o resumo pode ser enviado.
const certainties = new Set(["recorded", "proposed", "confirmed"]);
const nonempty = (value) => typeof value === "string" && value.trim() !== "";
const scalar = (value) => value === null || typeof value === "string" ||
typeof value === "boolean" || (typeof value === "number" && Number.isFinite(value));
const record = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
function validatePacket(packet) {
if (!record(packet) || !record(packet.sources) ||
!Array.isArray(packet.facts) || !Array.isArray(packet.claims)) {
throw new TypeError("Expected sources, facts and claims.");
}
if (!Object.entries(packet.sources).every(([id, text]) => nonempty(id) && nonempty(text))) {
throw new TypeError("Every source needs an ID and source text.");
}
for (const [kind, rows] of [["fact", packet.facts], ["claim", packet.claims]]) {
const seen = new Set();
for (const row of rows) {
if (!record(row) || !["id", "subject", "field"].every((key) => nonempty(row[key])) ||
!Object.hasOwn(row, "value") || !scalar(row.value) ||
!certainties.has(row.certainty) || seen.has(row.id)) {
throw new TypeError(`Invalid or duplicate ${kind} record.`);
}
seen.add(row.id);
if (kind === "fact" && (!nonempty(row.sourceId) ||
!Object.hasOwn(packet.sources, row.sourceId))) {
throw new TypeError(`Unknown source for fact ${row.id}.`);
}
if (kind === "claim" && (!nonempty(row.text) ||
!(row.evidenceId === null || nonempty(row.evidenceId)))) {
throw new TypeError(`Claim ${row.id} needs text and an evidence ID or null.`);
}
}
}
}
function compareClaimFields(packet) {
validatePacket(packet);
const facts = new Map(packet.facts.map((fact) => [fact.id, fact]));
return packet.claims.map((claim) => {
const fact = facts.get(claim.evidenceId);
const issues = [];
if (claim.evidenceId === null) {
issues.push("MISSING_EVIDENCE");
} else if (!fact) {
issues.push("UNKNOWN_EVIDENCE");
} else {
if (claim.subject !== fact.subject) issues.push("SUBJECT_MISMATCH");
if (claim.field !== fact.field) issues.push("FIELD_MISMATCH");
if (claim.value !== fact.value) issues.push("VALUE_MISMATCH");
if (claim.certainty !== fact.certainty) issues.push("CERTAINTY_MISMATCH");
}
return {
id: claim.id,
status: issues.length ? "needs_review" : "fields_match",
issues,
sourceId: fact?.sourceId ?? null,
proseReviewRequired: true,
};
});
}
function summaryRows(packet, report, language = "en") {
const pt = language === "pt-BR";
const messages = pt ? {
MISSING_EVIDENCE: "falta uma referência",
UNKNOWN_EVIDENCE: "referência desconhecida",
SUBJECT_MISMATCH: "o assunto é outro",
FIELD_MISMATCH: "o campo é outro",
VALUE_MISMATCH: "o valor é diferente",
CERTAINTY_MISMATCH: "o grau de certeza mudou",
} : {
MISSING_EVIDENCE: "missing evidence",
UNKNOWN_EVIDENCE: "unknown evidence",
SUBJECT_MISMATCH: "different subject",
FIELD_MISMATCH: "different field",
VALUE_MISMATCH: "different value",
CERTAINTY_MISMATCH: "certainty changed",
};
return report.map((result, index) => ({
id: result.id,
text: packet.claims[index].text,
result: result.status === "fields_match"
? (pt ? "CAMPOS CONFEREM" : "FIELDS MATCH")
: (pt ? "REVISAR" : "REVIEW"),
reason: result.issues.length
? result.issues.map((code) => messages[code]).join("; ")
: (pt ? "a redação ainda precisa de revisão" : "prose still needs review"),
}));
}
const packet = {
sources: {
"S1": "O rascunho da página de destino está pronto. Ainda não há aprovação do cliente.",
"S2": "Ana deve enviar o rascunho ao cliente na terça-feira, 27 de outubro de 2026.",
"S3": "A exportação da planilha passou em três verificações por amostragem. A importação no sistema seguinte ainda apresenta erro.",
"S4": "Possível lançamento na sexta-feira, 30 de outubro. Data proposta, não confirmada.",
"S5": "A página de ajuda precisa de um responsável. Ninguém foi designado.",
},
facts: [
{"id": "F1", "sourceId": "S1", "subject": "landing-page", "field": "client_approval", "value": "not_received", "certainty": "recorded"},
{"id": "F2", "sourceId": "S2", "subject": "send-draft", "field": "owner", "value": "Ana", "certainty": "recorded"},
{"id": "F3", "sourceId": "S3", "subject": "spreadsheet-export", "field": "sample_checks_passed", "value": 3, "certainty": "recorded"},
{"id": "F4", "sourceId": "S3", "subject": "next-system-import", "field": "status", "value": "error", "certainty": "recorded"},
{"id": "F5", "sourceId": "S4", "subject": "launch", "field": "date", "value": "2026-10-30", "certainty": "proposed"},
{"id": "F6", "sourceId": "S5", "subject": "help-page", "field": "owner", "value": null, "certainty": "recorded"},
],
claims: [
{"id": "C1", "text": "O lançamento está confirmado para 30 de outubro.", "subject": "launch", "field": "date", "value": "2026-10-30", "certainty": "confirmed", "evidenceId": "F5"},
{"id": "C2", "text": "Ana é a responsável pela página de ajuda.", "subject": "help-page", "field": "owner", "value": "Ana", "certainty": "recorded", "evidenceId": "F6"},
{"id": "C3", "text": "A importação passou em três verificações por amostragem.", "subject": "next-system-import", "field": "sample_checks_passed", "value": 3, "certainty": "recorded", "evidenceId": "F3"},
{"id": "C4", "text": "O cliente aprovou a página de destino.", "subject": "landing-page", "field": "client_approval", "value": "received", "certainty": "recorded", "evidenceId": "F1"},
{"id": "C5", "text": "30 de outubro é uma data proposta para o lançamento.", "subject": "launch", "field": "date", "value": "2026-10-30", "certainty": "proposed", "evidenceId": "F5"},
{"id": "C6", "text": "A página de ajuda não tem responsável designado.", "subject": "help-page", "field": "owner", "value": null, "certainty": "recorded", "evidenceId": "F6"},
{"id": "C7", "text": "A exportação passou em três verificações por amostragem.", "subject": "spreadsheet-export", "field": "sample_checks_passed", "value": 3, "certainty": "recorded", "evidenceId": "F3"},
{"id": "C8", "text": "A importação no sistema seguinte ainda apresenta erro.", "subject": "next-system-import", "field": "status", "value": "error", "certainty": "recorded", "evidenceId": "F4"},
{"id": "C9", "text": "Ana ficou encarregada de enviar o rascunho.", "subject": "send-draft", "field": "owner", "value": "Ana", "certainty": "recorded", "evidenceId": "F2"},
],
};
console.table(summaryRows(packet, compareClaimFields(packet), "pt-BR"));
Leia o resultado como uma comparação, não como autorização para enviar
As quatro primeiras afirmações precisam de revisão. As cinco seguintes correspondem aos campos da tabela de fatos.
| Afirmação | Resultado | O que mudou ou foi preservado |
|---|---|---|
| C1: lançamento confirmado | REVISAR | proposed virou confirmed |
| C2: Ana é responsável pela ajuda | REVISAR | null virou Ana |
| C3: importação passou em três verificações | REVISAR | A referência descreve a exportação |
| C4: cliente aprovou a página | REVISAR | A aprovação passou de ausente para recebida |
| C5: data proposta para o lançamento | CAMPOS CONFEREM | A data e a incerteza foram preservadas |
| C6: ajuda sem responsável | CAMPOS CONFEREM | Ninguém foi designado |
| C7: exportação passou em três verificações | CAMPOS CONFEREM | O número pertence à exportação |
| C8: importação ainda apresenta erro | CAMPOS CONFEREM | A etapa pendente continua visível |
| C9: Ana deve enviar o rascunho | CAMPOS CONFEREM | A atribuição continua ligada à tarefa certa |
Repare no que não aparece: nenhum resultado diz approved. Todos mantêm proseReviewRequired: true, indicando que a redação ainda precisa de revisão.
Quando a comparação falhar, corrija o rascunho ou investigue a anotação. Não altere a tabela só para fazer o texto passar. Se o plano mudou de verdade, confira a nova anotação e atualize a tabela com base nela. Em um sistema de trabalho, a tabela também precisa de uma data ou versão. Uma decisão antiga, mesmo copiada corretamente, pode ser a resposta errada hoje.
Agora faça o teste falhar de outro jeito
Em C5, troque apenas a frase por “O lançamento está confirmado para 30 de outubro”. Deixe o campo certainty como proposed.
O resultado continuará sendo CAMPOS CONFEREM.
Esse é o limite da verificação. Os campos correspondem à tabela, mas a frase contradiz os próprios campos. O código não interpreta text para decidir se ele é verdadeiro. Uma pessoa precisa conferir essa ligação, verificar se a tabela está atualizada e perceber se alguma informação importante ficou de fora. Cada registro deve representar uma afirmação. Separe frases com várias afirmações antes de verificá-las.
Faça outra mudança: use F2 como referência de C2. Esse fato fala de Ana enviando o rascunho. O nome agora corresponde, mas o assunto é outro. Ter a tarefa de enviar um documento não torna Ana responsável pela página de ajuda.
Esse tipo de verificação pode ficar ao lado de uma etapa de redação com IA, de um formulário ou de uma exportação de planilha. Sua contribuição é uma lista curta de divergências que você consegue inspecionar. Quem revisa o resumo pode se concentrar na decisão, em vez de procurar de onde saiu uma palavra alterada.
Para praticar mais, o exercício em inglês com cinco afirmações e uma ficha editável usa as mesmas anotações. Comece pela frase que alguém da equipe provavelmente vai usar para agir. Confira se a data, o responsável e o grau de certeza têm respaldo nas anotações.