Skip to content

fix(ir): corrigir um trecho gasta orçamento de reconto - #297

Merged
henokteixeira merged 2 commits into
mainfrom
henok/eng-685-corrigir-gasta-orcamento-de-reconto
Sep 7, 2026
Merged

henokteixeira merged 2 commits into
mainfrom
henok/eng-685-corrigir-gasta-orcamento-de-reconto

Conversation

@henokteixeira

Copy link
Copy Markdown
Contributor

Fecha ENG-685. Independente da pilha 676/679/680 — sai da main e vai para a main.

Summary

MAX_RETELLS = 3 existe para que a equipe nunca fique presa recontando: quando o orçamento
acaba, a sala para e pede uma pessoa. Ele era cobrado num lugar só — POST /back-translation/chunks, e apenas com retelling=true.

A correção de um trecho não passa por ali. Passa por POST /segments/{id}/replace, e essa
rota não tocava no orçamento. O único limite que a sala tem contra prender uma equipe recontando
não cobria o caminho pelo qual a equipe de fato corrige — e numa sala onde ninguém lê e o
facilitador é uma voz, uma equipe presa nesse ciclo não tem como pedir ajuda.

Esta fatia torna as duas rotas simétricas: somar, gravar o estado, e pedir uma pessoa quando
estourar. Escolhida a saída (a) da issue; a (b) foi descartada.

A decisão de desenho: qual das duas formas de substituir gasta

Substituir tem duas formas, e só a que traz uma explicação nova gasta — /replace com
arquivo. A chamada sem arquivo, que regrava o áudio nativo, não gasta.

Não é preferência: é o que o próprio código já dizia. O docstring de capture_segment:

"the product has two corrections and not three: redoing only the explanation, which leaves
the native audio exactly where it is, and re-recording the native, which always means the
explanation is redone after it
."

Regravar o nativo nunca é uma correção completa — é sempre seguido de uma segunda chamada, com
arquivo, que traz a explicação. São dois passos de uma tentativa. Contar os dois cobraria
duas unidades por uma correção, e a equipe chegaria ao fim do orçamento em uma volta e meia,
punindo justamente a correção mais completa.

E alinha com o vizinho: em /chunks quem marca o gasto é retelling, documentado como "telling
one stretch back a second time after a finding"
. O orçamento é sobre contar de novo, não
sobre gravar de novo. Com esta escolha, as duas formas de corrigir custam exatamente 1.

Duas consequências que decorrem dela:

  • Requisição inválida não gasta. Fatia movida com áudio junto, ou payload acima do limite,
    são recusados antes de qualquer gasto — a rota já argumenta que "what is not stored first is
    a request that cannot succeed"
    . Requisição malformada não é tentativa da equipe.
  • Transcrição vazia gasta, pelo argumento que a rota irmã já carrega por escrito: se só o
    sucesso contasse, uma queda do transcritor deixaria a equipe corrigindo para sempre e a rota
    para uma pessoa ficaria inalcançável exatamente quando a sala está quebrada.

⚠️ Isto ainda não muda o que a equipe vê no caminho de /replace

SegmentsResponse ganhou needs_person, como BackTranslationChunkResponse já tinha. O app
não lê esse campo
— TellingAgain.fromJson lê apenas segments e captured — então por
enquanto o servidor avisa e o cliente descarta o aviso em silêncio. O lado do app está sendo
feito em paralelo; até ele chegar, o comportamento visível nesta rota não muda.

O aviso não se perde inteiramente, e isso importa para avaliar o risco. mark_needs_person
grava o estado na própria sessão (status = needs_person), que GET /sessions/{id} serve. A
parada é persistente, não uma menção única numa resposta: qualquer leitura seguinte do estado a
encontra. O caso test_the_room_that_stopped_says_so_in_its_own_state guarda exatamente isso.

Test plan

Sete casos em tests/test_internalization_room_segment_verbs.py, todos pela rota, perguntando o
que o orçamento marcou e o que a sala passou a oferecer — nenhum lê estado interno.

Caso O que guarda
test_correcting_a_stretch_spends_retell_budget Corrigir gasta
test_the_budget_runs_out_and_the_room_offers_a_person O estouro chama uma pessoa, e a resposta daquela correção não se perde
test_the_budget_is_spent_on_the_attempt_not_on_the_result Gasto na tentativa, não no resultado
test_telling_a_new_stretch_never_spends_retell_budget O controle mais importante
test_the_telling_back_route_still_counts_the_way_it_counted A rota vizinha não muda
test_dividing_a_stretch_is_not_correcting_and_spends_nothing Dividir não é recontar
test_the_room_that_stopped_says_so_in_its_own_state A parada sobrevive à resposta que a carregou

RED antes da implementação: 1, 2 e 3 vermelhos (assert 0 == (0 + 1), assert 0 >= 3,
assert 0 == (0 + 1) — o orçamento parado em zero); 4 e 5 verdes. Os dois últimos casos foram
acrescentados por mim para os casos de borda do plano.

Casos de borda

  • Dividir um trecho não gasta — é a equipe ouvindo duas ideias onde contou uma, escrevendo
    duas linhas contra um áudio que já estava lá. Nenhum áudio cruza a rede e nada é recontado;
    cobrar seria gastar o orçamento da equipe num ato de leitura. Com caso próprio.
  • Sessão retomada — o número vive no estado guardado da sessão, e o caso do estouro já o
    demonstra: são três requisições HTTP separadas e o número acumula entre elas.
  • Estouro exatamente na última correção permitida — a equipe recebe os trechos daquela
    correção e a marca de parada. Perder a resposta seria pior que o problema; asseverado no
    caso do estouro.

Números

Baseline medida na main atual antes de tocar em nada: 2344 passed, 5 skipped, 1 xfailed.
Depois: 2351 passed, 5 skipped, 1 xfailed — a baseline mais exatamente os sete novos.
ruff check, ruff format e mypy app limpos (596 arquivos).

…ld have

`MAX_RETELLS` exists so a team never gets stuck retelling: when it runs out the
room stops and asks for a person. It was charged on the telling-back route
alone, and a correction does not go through there — it goes through replace,
which touched neither the budget nor the request for a person. The one limit the
room has against trapping a team did not cover the path the team corrects by,
and in a room where nobody reads and the facilitator is a voice, a team stuck in
that cycle has no way to ask for help.

Only the call that carries a new explanation counts. The service says why in its
own words: the product has two corrections and not three, and re-recording the
native always means the explanation is redone after it — so that is two steps of
one attempt, and charging both would take two from the budget for a single
correction, ending the budget in a round and a half and punishing the more
complete correction. The neighbour agrees: what marks a spend there is
`retelling`, telling one stretch back a second time. The budget is about telling
again, not about recording again.

Spent on the attempt and not on the result, for the reason already written on
the neighbouring route: if only a correction that landed counted, then during a
transcriber outage — when every attempt comes back empty — the team could
correct forever, the budget would never run out, and the room's only route to a
person would be unreachable exactly when the room is broken.

A request that cannot succeed still costs nothing. A slice that moved arriving
with audio, or a payload over the limit, are refused before anything is spent:
a malformed request is not an attempt by the team.
Seven cases through the route, asking what the budget marked and what the room
went on to offer. The one that matters most is the control: telling a new
stretch back never spends a retell. Without it this becomes "every recording
sent spends", and a team telling six stretches back for the first time would be
handed to a person without having retold anything — punishing the path where
nothing went wrong.

Two more for the edges. Dividing is the team hearing two ideas where they told
one: two rows against a recording that was already there, no audio over the
wire, nothing retold — charging it would spend a team's budget on an act of
reading. And the room that stopped says so in its own state, not only in the
reply that carried it: a client ignoring the field would otherwise lose the one
moment the room asked for help.
@linear-code

linear-code Bot commented Sep 1, 2026

Copy link
Copy Markdown

ENG-685

@henokteixeira
henokteixeira merged commit f9a5c86 into main Sep 7, 2026
5 checks passed
@henokteixeira
henokteixeira deleted the henok/eng-685-corrigir-gasta-orcamento-de-reconto branch September 9, 2026 18:22
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