Banco de Exercícios — sintaxe, tipos e como a correção funciona
Documento de referência (Diátaxis). O porquê está na constituição (Princípio VIII); o como escrever bem está no Guia Editorial §4. Aqui está a mecânica.
Por que a correção mora no servidor
A página que você lê não conhece a resposta certa. Quando você clica em "Responder", a resposta viaja até o backend do livro, que corrige, explica e devolve o feedback.
Três razões, nesta ordem:
- Feedback melhor. O servidor sabe quantas vezes você já tentou. Na primeira tentativa errada ele explica o conceito e devolve você à seção certa; só na segunda revela a resposta esperada. Um gabarito embutido no HTML não consegue esperar.
- A avaliação por rubrica só existe lá. Respostas abertas são avaliadas contra critérios escritos pelo autor — isso exige um modelo, e um modelo exige um servidor.
- O erro é o sinal mais valioso deste projeto. Saber qual exercício erra mais, e com que resposta, é o que corrige o livro. Exercício com taxa de acerto muito baixa é sintoma de texto mal escrito — e entra na fila de revisão (Guia §10).
O livro é aberto: quem quiser ver o gabarito acha no repositório. Não estamos escondendo — estamos evitando que a resposta esteja a um Ctrl+U de distância no momento em que você deveria estar pensando.
Sem backend configurado, o exercício continua legível e diz isso honestamente. O livro nunca finge ter corrigido.
Onde os exercícios vivem
Dentro do Markdown do capítulo, junto do conteúdo que eles testam — não num arquivo separado. Isso garante que exercício e texto envelheçam juntos.
O motor faz dois recortes do mesmo bloco:
| Consumidor | O que recebe |
|---|---|
publicar/build.mjs → a página |
enunciado + alternativas, sem marcação de correta, sem feedback |
publicar/exercicios.mjs → banco.json → o backend |
tudo, inclusive gabarito, feedback e rubrica |
A fonte única da sintaxe é publicar/interativos.mjs.
Sintaxe
Um bloco começa com :::exercicio seguido de atributos JSON e termina com :::.
:::exercicio {"id":"avaliacao-e1","tipo":"multipla","objetivo":"O2","dificuldade":"media"}
Numa base de detecção de fraude com 0,3% de casos positivos, um modelo atinge
99,5% de acurácia. Qual leitura é correta?
- [ ] O modelo é excelente: erra menos de 1 em 200.
- [x] O número não diz nada: prever "não é fraude" sempre já daria 99,7%.
- [ ] A acurácia é inválida para problemas binários.
> **gabarito:** O número não diz nada
> **porque:** Com 0,3% de positivos, a classe majoritária sozinha entrega 99,7% de
> acurácia — o modelo com 99,5% está **abaixo** do classificador que não faz nada.
> Acurácia mede acertos totais e, quando uma classe domina, ela mede sobretudo a
> prevalência. Para esse caso, olhe precisão e revocação da classe rara.
> **volte para:** #os-cinco-tipos
:::
Atributos
| Atributo | Obrigatório | O que é |
|---|---|---|
id |
sim | único no livro inteiro. Convenção: NN-eK (capítulo, exercício). Duplicata quebra o build. |
tipo |
sim | multipla, multipla-multi, numerica, completar, aberta |
objetivo |
sim | o O1/O2… declarado na seção Objetivos do capítulo. O build confere que existe. |
pontos |
não | peso (default 1) |
dificuldade |
não | facil, media, dificil (default media) |
versoes |
não | quando o gabarito depende de versão: {"scikit-learn":"1.5"} (Princípio IV) |
Metadados do rodapé
Linhas de citação > **chave:** valor, aceitando continuação em múltiplas linhas.
| Chave | Quando | O que faz |
|---|---|---|
gabarito |
numérica, completar | a resposta esperada |
porque |
sempre | o feedback explicativo. Sem ele o build falha. |
volte para |
recomendado | âncora da seção que resolve a dúvida (#slug-do-titulo) |
rubrica |
aberta | critérios separados por ; ou um por linha; mínimo 2 |
Os cinco tipos
multipla — escolha uma
Exatamente uma alternativa marcada com [x]. Mínimo duas alternativas.
multipla-multi — escolha todas que valem
Uma ou mais corretas. Marcar um subconjunto das corretas (sem marcar nenhuma errada) conta como parcial — o leitor vê "◐ parcialmente" e é convidado a completar.
numerica — responda com um número
O gabarito aceita tolerância explícita:
> **gabarito:** 0.80 ± 0.01
Sem tolerância declarada, exige-se o valor exato. A resposta é lida de forma tolerante a formato: 0,80, 0.80 e 80% chegam ao mesmo lugar. Fora da tolerância mas dentro do dobro dela (ou de 10%, quando a tolerância é zero), o resultado é parcial — o leitor errou a conta, não o conceito.
completar — complete a lacuna
Completion problem no sentido de Sweller: o andaime está posto, falta a peça. Alternativas aceitas separadas por |. A comparação ignora caixa, acentos e espaços extras.
:::exercicio {"id":"otimizacao-e2","tipo":"completar","objetivo":"O3"}
Complete o termo que impede os pesos de crescerem sem limite:
`perda_total = perda_de_dados + λ · ______(w)`
> **gabarito:** regularização|regularizacao|penalidade
> **porque:** O segundo termo penaliza a complexidade do modelo...
:::
aberta — responda com suas palavras
Avaliada pelo modelo contra a rubrica escrita pelo autor — nunca contra "o que o modelo acha que é uma boa resposta". O leitor recebe a lista de critérios com ✔ e ○, e um comentário curto.
:::exercicio {"id":"fundamentos-e3","tipo":"aberta","objetivo":"O4","pontos":3}
Explique, para uma pessoa de produto, por que um modelo com 99% de acurácia
no teste pode falhar no primeiro mês em produção.
> **rubrica:** distingue distribuição de treino e de produção;
> menciona ao menos um mecanismo concreto (drift, vazamento, viés de seleção);
> não atribui a falha a "pouco dado" sem justificar;
> propõe uma forma de detectar o problema antes do usuário
> **porque:** O ponto central é que a métrica de teste só vale sob a hipótese de
> que produção se parece com o teste...
:::
Sem modelo configurado, a rota devolve uma avaliação honesta-vazia: diz que não avaliou, e lembra os critérios. Nunca inventa uma nota.
Vídeos
:::video {"id":"avaliacao-v1","fonte":"youtube","ref":"4jRBRDbJemM","min":16,"autor":"StatQuest","titulo":"ROC and AUC, clearly explained"}
Resolve a intuição **geométrica** da curva ROC — o texto trata do trade-off
algebricamente, e ver o limiar deslizando faz a moeda cair.
:::
| Atributo | Obrigatório | O que é |
|---|---|---|
id |
sim | único; convenção NN-vK |
fonte |
não | youtube (default) ou vimeo |
ref |
sim | o identificador do vídeo na fonte |
autor |
sim | crédito de quem produziu |
titulo |
não | título exibido (default: o id) |
min |
não | duração aproximada |
O corpo do bloco é a justificativa obrigatória: o que este vídeo resolve que o texto não resolve. Vídeo sem justificativa não compila.
O player é uma fachada: nada é pedido ao servidor de origem antes do clique do leitor.
Laboratórios
A terceira superfície: exercício pergunta e corrige, vídeo mostra, laboratório deixa manipular.
:::lab {"id":"neuronio-artificial-l1","tipo":"neuronio-mp","titulo":"Neurônio de McCulloch–Pitts","funcao":"AND"}
O que manipular aqui ensina — e o que o leitor deve **descobrir sozinho**.
:::
| Atributo | Obrigatório | O que é |
|---|---|---|
id |
sim | único no livro; convenção NN-lK |
tipo |
sim | qual widget carregar (ver tabela abaixo) |
titulo |
não | exibido no cabeçalho |
| demais | — | passados ao widget como configuração inicial |
O corpo do bloco é a introdução obrigatória. Laboratório sem ela não compila.
Widgets disponíveis
tipo |
O que faz | Configuração |
|---|---|---|
neuronio-mp |
Neurônio de McCulloch–Pitts: pesos e limiar à mão, reta de decisão desenhada em tempo real sobre a tabela-verdade | funcao: AND, OR, NAND, NOR, XOR |
Novos widgets entram em publicar/tema/laboratorios.js, registrados no objeto TIPOS do fim do arquivo.
Por que laboratório não precisa de backend
Não há resposta a esconder: o gabarito é o comportamento do objeto. Isso o torna a superfície mais robusta do livro — funciona offline, funciona sem servidor, e continua funcionando quando tudo o mais falha.
Interações
A quarta superfície, e a única que revela no cliente. A distinção que a define, e que decide toda a arquitetura:
:::exercicio |
:::interacao |
|
|---|---|---|
| Função | somativa — vale nota | formativa — não vale nada |
| Correção | no backend; o gabarito nunca vai ao cliente | no cliente, revelando |
| Registro | grava tentativa por aluno, e aprende do erro | não grava nada — nem servidor, nem localStorage |
| Quando o leitor erra | conta contra ele | é o ponto |
É porque a interação não vale nota que ela pode revelar no cliente sem violar o Princípio VIII.3. Aquele princípio protege o gabarito daquilo que é contabilizado; onde não há tentativa, placar nem telemetria, não existe segredo a guardar. E a escolha compra uma garantia: sem segredo não há chamada de rede, e a interação continua inteira com o backend fora do ar (Princípio VIII.6).
A premissa do autor é que todo cartão tem uma interação e um exercício — quem cobra isso é publicar/gates/cartoes-legiveis.mjs, que conta .interacao e [data-interacao] no DOM.
:::interacao {"id":"modelos-lineares-i1","tipo":"prever","titulo":"O peso do ponto distante","numero":100}
Um ponto erra por **1**; o *outlier* erra por **10**. No critério absoluto o segundo pesa 10 vezes mais.
> **pergunta:** E no quadrático, quantas vezes?
> **revela:** **Cem vezes.** O erro decuplicou e o peso centuplicou.
:::
Atributos
| Atributo | Obrigatório | O que é |
|---|---|---|
id |
sim | único no livro inteiro; convenção <capitulo>-iK. O build recusa duplicata |
tipo |
sim | principio, desvanecido ou prever |
titulo |
não | exibido no cabeçalho do bloco |
numero |
só prever numérico |
o valor real, contra o qual a previsão é comparada |
tolerancia |
não | margem aceita em torno de numero (padrão 0) |
Metadados do rodapé
| Chave | Quando | O que faz |
|---|---|---|
revela |
sempre | o que aparece depois do clique. Sem ele o build falha |
pergunta |
principio e prever |
o que o leitor responde antes de revelar |
Marcadores no corpo
| Marcador | Tipo | O que é |
|---|---|---|
- [?] rótulo => a linha certa |
desvanecido |
um passo apagado da conta |
- ( ) texto |
prever |
uma opção de previsão |
- (!) texto |
prever |
a opção que é o que de fato acontece |
O marcador do passo não é - [x], e por dois motivos: [x] significa "gabarito" nesta casa, e interação não tem gabarito; e o gate de vazamento do build.mjs recusa qualquer - [x] no Markdown exportado, que semGabarito() só limpa dentro de bloco de exercício.
Os três tipos, e a evidência de cada um
Os números abaixo são resumo. A fonte, o selo e a fronteira de cada achado estão em BASE-EDUCACIONAL.md, e quatro dos cinco ainda estão em ⏳.
tipo |
O gesto | Apoio |
|---|---|---|
principio |
exemplo trabalhado com pergunta de princípio. O leitor escreve, clica, e a explicação aparece ao lado da resposta dele — que não é corrigida, é comparada | autoexplicação provocada supera receber a explicação pronta (g=0,35; Bisra et al., 2018) |
desvanecido |
passo apagado da conta. Ao conferir, as linhas certas aparecem e as dele ficam ao lado. Sem nota, sem "errado" | desvanecimento somado a prompt de princípio rende em transferência próxima e distante (Atkinson, Renkl & Merrill, 2003) |
prever |
pergunta com 2 ou 3 opções, ou campo numérico, que tem de ser respondida antes de o botão liberar. A revelação repete a previsão dele e diz se bateu | resolver antes de explicar rende (g=0,36; Sinha & Kapur, 2021), desde que a explicação construa sobre o que o leitor tentou — g=0,56 contra 0,20 quando ignora |
A última coluna é a razão de a revelação do prever começar pela previsão do leitor, e não pela resposta.
Acessibilidade, e uma armadilha
O botão de revelar não nasce disabled nem aria-disabled enquanto falta responder. As duas coisas o tiram da tabulação ou o anunciam como indisponível — e aí o motivo de ele não liberar, que mora no role="status" ao lado, deixa de ser alcançável por quem lê a tela. (Medido: o Playwright recusa clicar num aria-disabled, aplicando a mesma regra que a tecnologia assistiva.) O sinal de "ainda não" é data-pronto, que pinta e não bloqueia; quem bloqueia é o clique, que escreve o porquê. A revelação entra num aria-live="polite" que já existe vazio no DOM, porque região viva criada na hora não anuncia de forma confiável.
O código: publicar/tema/interacoes.js (comportamento) e publicar/testes/interacoes.mjs (o que ele promete). DOM falso não tem foco nem tabulação, e foi por aí que o aria-disabled passou. Por isso a asserção F de publicar/jornada.mjs refaz o gesto num Chromium de verdade: clica em revelar sem responder nada e exige que nada tenha sido revelado e que a página tenha dito por quê.
Aprofundamentos
A quinta superfície, e a única que tira conteúdo do caminho do leitor. O :::aprofundar guarda a dedução pesada dentro de um <details> fechado. Quem quer a conta clica; quem quer o conceito segue adiante sem rolar por cima dela.
:::aprofundar {"titulo":"De onde sai o 2/n"}
A conta inteira, para quem quer. Markdown normal aqui dentro: prosa, fórmula, tabela, código.
:::
| Atributo | Obrigatório | O que é |
|---|---|---|
titulo |
sim | o texto do <summary>, e a única pista que o leitor tem do que está fechado ali |
Por que ele existe
Três coisas se encontram no mesmo ponto. O cartão tem teto de 1.600 px e de 250 palavras, e a dedução é o que mais encosta nele. A dívida D21 foi paga quebrando fórmula em duas linhas, o que aumenta a altura do cartão. E a carga cognitiva (Sweller, ✓ em BASE-EDUCACIONAL.md) pede uma ideia nova por vez, sendo a derivada completa a segunda ideia da página.
O que ele promete, e como isso foi medido
É um <details> nativo, sem uma linha de JavaScript: o foco e o teclado vêm do navegador, e o bloco continua inteiro com o backend fora do ar (Princípio VIII.6). Os navegadores atuais também abrem um <details> fechado quando o Ctrl+F acha texto lá dentro, e esta é a única promessa da lista que não foi medida aqui: busca na página é interface do navegador, e não se dispara por script. Ele nasce fechado, porque é aprofundamento e não conteúdo escondido: o fluxo principal fica completo sem ele.
Fechado, o corpo não entra no innerText que publicar/gates/cartoes-legiveis.mjs usa para contar as palavras de um cartão. Medido num Chromium 141 a 360×800: 15 palavras fechado contra 29 aberto no mesmo cartão, e 70 px contra 138 px de altura. A medição vive em publicar/testes/aprofundar.mjs, roda num navegador de verdade e cobra as duas direções, porque uma asserção que só olhasse o lado fechado passaria com um bloco que nunca abre.
O que o parser recusa, e por quê
Um aprofundamento que guardasse exercício, interação, laboratório, vídeo ou corte de cartão seria perda de conteúdo com cara de organização. E seria silenciosa: o gate dos cartões acha .exercicio e .interacao com querySelectorAll, que atravessa <details> fechado. O cartão passaria no portão da premissa do autor exibindo uma linha fechada, e o leitor ficaria sem nada para fazer. Por isso nenhum bloco ::: vive dentro de um :::aprofundar, e a recusa é do parser.
Há um limite herdado, e ele tem mensagem de erro própria: o corpo termina na primeira linha que seja só :::, ainda que dentro de cerca de código. A regra é do RE_BLOCO de publicar/interativos.mjs, que é a fonte única da sintaxe da casa. Um exemplo de bloco completo mora fora do aprofundamento; código sem ::: solto é aceito normalmente, inclusive com linha em branco no meio.
Progresso do leitor
- Identidade anônima, gerada pelo navegador — a mesma do chat. Sem cadastro, sem email.
- Espelhada em
localStorage, para a barra de progresso funcionar mesmo offline. GET /progresso?session_id=…devolve o que aquela sessão resolveu.DELETE /session/{id}apaga tudo — conversas, tentativas e vídeos. Direito ao esquecimento, sem pedido, sem formulário.
Gate de qualidade
cd publicar
node exercicios.mjs --verificar # valida sem escrever nada (é o gate da CI)
node exercicios.mjs # gera chat-companion/backend/banco.json
O gate falha quando:
- falta
id,tipo,objetivo, enunciado ouporque; - o
objetivonão existe entre os declarados no capítulo; - múltipla escolha não tem exatamente uma correta;
- numérica tem gabarito ilegível;
- aberta tem menos de 2 critérios;
- há
idde exercício ou de vídeo duplicado; - vídeo sem
ref, semautorou sem justificativa; - aprofundamento sem
titulo, com corpo vazio ou com um bloco:::aninhado dentro.
Nenhum desses é aviso. Todos são erro de build — porque um exercício quebrado é pior que exercício nenhum.