Skip to content

feat(sala): a resposta diz qual trecho ainda não foi contado - #299

Merged
henokteixeira merged 4 commits into
mainfrom
henok/the-room-names-the-untold-stretch
Sep 7, 2026
Merged

henokteixeira merged 4 commits into
mainfrom
henok/the-room-names-the-untold-stretch

Conversation

@henokteixeira

@henokteixeira henokteixeira commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Summary

🤖 Generated with Nori

Quando a equipe grava um trecho na língua materna mas nunca o conta na língua-ponte, o servidor já parava com o fail-safe da família H (UNTOLD_STRETCH) — mas a resposta não dizia qual trecho faltava. Sem endereço, o app só tinha um movimento: devolver a equipe à tela de ensaio, o que apaga todas as gravações da passagem. Uma explicação faltando custava a manhã inteira.

  • first_untold(segments) em app/services/internalization_room/segments.py — o primeiro trecho, na ordem da passagem, que conta e não carrega retrotradução. Fica ao lado de told_back de propósito: a contagem que trava o analista e o endereço para onde a equipe é mandada saem da mesma lista e não podem discordar.
  • finish() (app/api/internalization_room/back_translation.py) passa a devolver esse endereço junto com a linha da família H.

Nada da fala mudou. Nenhum arquivo de prompt e nenhuma linha falada foi tocado — o diff são quatro arquivos, todos de código.

O campo que a fatia do app vai ler

untold_segment_id: str | None = None

Em BackTranslationVerdictResponse (app/models/internalization_room.py). É o IRSegment.id (UUID em texto), o mesmo endereço que finding_segment_id usa. Vem preenchido apenas na resposta do fail-safe H; em todo o resto do caminho fica null.

Na resposta do trecho não contado: checked: false, findings_remaining: 0, finding_kind: null, finding_segment_id: null, audio_url com a linha H sintetizada, fixed_line: "".

Por que campo próprio e não reusar finding_segment_id. Os dois pedem coisas diferentes da sala. Um achado é um trecho que a equipe contou e sobre o qual o analista tem uma correção — o app toca o achado e pede para recontar. Um trecho não contado não tem retrotradução nenhuma — o app precisa abrir a gravação da língua-ponte pela primeira vez. São telas diferentes. Reusar um campo obrigaria o app a inferir qual dos dois casos é pela ausência de finding_kind, e inferir significado pela ausência de um dado é exatamente o que produziu este defeito. Os dois campos são mutuamente exclusivos por construção: o caminho do H retorna antes do analista rodar.

Compatível com app antigo: campo novo, default null, nenhum campo existente mudou de nome, tipo ou significado. Sem migração — o dado já existia nas colunas atuais.

Test plan

Cinco casos novos em tests/test_ir_the_room_names_the_untold_stretch.py, todos pela rota, do jeito que o app a chama:

  • Um trecho não contado é endereçado.
  • Com dois buracos abertos, é o primeiro na ordem da passagem — o trecho posterior é regravado primeiro de propósito, para que a ordem das linhas na tabela seja o inverso da ordem em que a equipe conta.
  • O fail-safe continua disparando: checked falso, o analista não lê subconjunto, e a linha falada continua vindo de utterances(FailSafe.UNTOLD_STRETCH, ...).
  • Com tudo contado, o caminho normal de veredito é idêntico e nenhum trecho é nomeado.
  • Uma versão retirada que nunca recebeu retrotradução não é apontada como buraco.
  • (endurecimento, além do plano) Um trecho dividido em dois é endereçado pela metade da frente — o caso em que a ordem da passagem deixa de ser uma coluna e vira uma caminhada pela hierarquia.

Cada caso foi provado ser um portão de verdade por mutação, e cada mutação é pega exatamente pelo caso que deveria pegá-la:

Mutação na implementação Quem reprova
Nomear o último não contado em vez do primeiro o caso da ordem e o do trecho dividido
Olhar a tabela crua, versões retiradas incluídas só o caso da versão retirada
Não pôr endereço nenhum na resposta (o comportamento de antes) cinco dos seis; passa só o caso em que tudo foi contado
Desligar o portão cinco dos seis

O caso que protege o fail-safe passava na RED na primeira versão — só afirmava coisas que já estavam certas, o que é um portão incapaz de reprovar. Foi endurecido para afirmar o que é novo (endereço não vem com checked verdadeiro nem com achado do analista) e agora reprova contra o comportamento de antes da fatia.

Para verificar:

JWT_SECRET_KEY=test-secret-for-pytest-only DATABASE_URL="sqlite+aiosqlite:///./test.db" \
  uv run pytest tests/ -q

Suíte inteira: 2350 passaram, 5 skipped, 1 xfailed, 0 falhas (9m00s). ruff check, ruff format --check e mypy app/ limpos.

Achados fora do escopo desta fatia — registrados, não consertados

Encontrados na autorrevisão e verificados no código. Nenhum deles é tocado por este PR.

  1. release.py:153 pode publicar um trecho com "text": null. A variável local se chama told_back, mas recebe await final_segments(...) — a lista inteira, contada ou não. O bloqueador no_telling_back (:170) só testa se a lista está vazia, e _segment_view (:88) serializa "text": segment.transcript. Uma sessão que contou tudo, foi analisada limpa, regravou um trecho e nunca o recontou passa pelos dois bloqueadores e entrega o artefato ao Refine com um trecho de texto nulo. first_untold é exatamente o predicado que esse bloqueador quer.
  2. used_fail_safe continua False no ramo do trecho não contado, embora a linha venha de choose(FailSafe.UNTOLD_STRETCH, ...). Pré-existente; quem apura telemetria por esse campo subconta este caminho.
  3. add_chunk:74 chama de told o resultado de final_segments — que inclui os não contados. Comportamento correto, nome enganoso agora que told_back e first_untold moram no mesmo módulo.
  4. O docstring de tests/test_ir_the_analyst_reads_all_of_it_or_none.py:8 afirma que regravar a materna é o único jeito de ter um trecho final sem explicação. Dividir um trecho também é — as duas metades nascem sem retrotradução. Já estava desatualizado antes desta fatia.

Share Nori with your team: https://www.npmjs.com/package/nori-skillsets

`first_untold` é o complemento de `told_back` sobre a mesma lista, e fica ao
lado dela para que a contagem que trava o analista e o endereço para onde a
equipe é mandada não possam discordar.

O primeiro na ordem da passagem, não um qualquer: a equipe conta a passagem
na sequência dela, e mandá-la para um buraco no meio enquanto há um anterior
aberto inverte a ordem do próprio trabalho.

Receber a lista em vez do id da sessão é o que mantém uma versão retirada
fora da resposta: só trechos que já contam são olhados.
O fail-safe da família H já sabia que faltava um trecho; não dizia qual. Sem
endereço, o app só tinha um movimento — devolver a equipe à tela de ensaio,
que apaga todas as gravações da passagem. Uma explicação faltando custava a
manhã inteira.

`untold_segment_id` ganha campo próprio em vez de reusar `finding_segment_id`
porque os dois pedem coisas diferentes da sala: um achado é um trecho que a
equipe contou e sobre o qual o analista tem uma correção; este é um trecho sem
nenhuma retrotradução. Ler um pela ausência do outro é exatamente a inferência
que causou o defeito.

Nada da fala muda: a linha da família H, o `checked` falso e o analista que não
lê subconjunto continuam como estavam.
Os cinco casos do critério de aceite, pela rota, do jeito que o app a chama.
Cada um foi provado ser um portão de verdade por mutação: nomear o último em
vez do primeiro derruba só o caso da ordem; olhar a tabela crua derruba só o
caso da versão retirada; desligar o portão derruba o caso que protege o
fail-safe, que passava de saída.
O parágrafo do docstring dizia uma garantia que `first_untold` não dá: entregue
a ela `retired_segments(...)` e ela aponta uma versão retirada. A propriedade é
de `final_segments`, e repeti-la aqui é a duplicação contra a qual o docstring
do próprio módulo avisa.

O caso que protege o fail-safe só afirmava coisas que já passavam antes desta
fatia — um portão incapaz de reprovar. Agora afirma o que é novo: que ter
endereço não vem junto com `checked` verdadeiro nem com um achado do analista.
Reprova com a resposta de antes da fatia.

E dividir um trecho é o outro jeito de abrir um buraco: as duas metades nascem
sem nada que a equipe disse e ficam onde o pai estava, que é onde a ordem da
passagem deixa de ser coluna e vira caminhada. Caso novo, além do plano.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant