Repository navigation
fix(zones): projeto pre-gravado adotava arena e ROIs de estudo anterior - #556
Merged
Merged
Conversation
Um projeto pre-gravado criado sobre uma pasta que ja continha 1_ProcessingArea_*/2_AreasOfInterest_* de um estudo anterior carregava aqueles poligonos no primeiro duplo-clique, apesar de a importacao ter sido RECUSADA no wizard. Nada levantava, nada degradava visivelmente: o relatorio saia completo, medido contra a arena do experimento anterior. Duas causas independentes, ambas silenciosas. 1. DialogManager.offer_zone_reuse chamava import_zone_data_from_video_parquets sob uma unica condicao -- "este video nao tem zonas em project_data?" -- e nunca consultava a decisao do wizard. O metodo foi escrito para gravacoes LIVE (o recorder grava os parquets na pasta da sessao enquanto as zonas em memoria ficam sob a chave do reference-frame) e a regra valia para todos. Agravante: scan_input_paths registra parquet_files na entrada do video mesmo com a recusa, e _resolve_source_zone_parquets varre Path(video).parent de qualquer forma -- limpar um nao fecha o outro. 2. ZoneManager.save_zone_data escrevia project_data["detection_zones"] incondicionalmente, mesmo com video_path explicito. O default global e o que get_zone_data(fallback_to_global=True) serve a qualquer video sem zonas proprias, entao a arena do video A virava a arena do video B. A decisao agora e de core/services/zone_autoimport_policy, por PROCEDENCIA: dentro do projeto => AUTO (sessao live, reprocessamento); fora do projeto com recusa registrada em _wizard_metadata["import_config"] => SKIP; fora sem registro => ASK. Sem projeto (video unico, live ad-hoc) => AUTO, legado intacto. import_zone_data_from_video_parquets passa a importar incondicionalmente: a permissao e de quem chama. O ramo ASK abre um dialogo que NOMEIA a pasta de origem -- era justamente a informacao ausente que fazia o import parecer configuracao escondida -- e avisa, depois de importar, para conferir os contornos. O espelho global vira ZoneManager.should_mirror_to_global(). Ele e mantido quando nao ha video alvo (o save E o global), quando project_type == "live" (uma camera, uma arena, e os .mp4 gravados nao tem chave propria: ZoneContext Service, ValidationManager e a grade de sessoes planejadas leem o global) e quando nao ha project_type declarado (video unico e live ad-hoc). So um projeto que se declara nao-live e excluido -- a restricao e pelo caso provado errado, nunca pela negacao do caso conhecido. Corrige tambem um crash latente pre-existente no fluxo de video unico: save_project() levanta ProjectInvalidError sem project_path, e o import chegava la depois de ter importado com sucesso. Verificacao: pytest -q 6695 passed / 0 failed; pytest -m gui -n0 1206 passed; mypy . limpo em 902 arquivos; ruff limpo; tests/i18n 79 passed; mutation_check --module zone_autoimport_policy 5/5 killed. i18n: as 4 strings novas foram anexadas ao .pot/.po e o .mo recompilado, em vez de rodar update_translations.py -- o catalogo em main esta completo mas defasado do fonte (8 msgids nunca extraidos), e regenerar arrastaria ~2600 linhas de churn alheio para dentro deste fix. O par ficou registrado em locales/_pairs/ para a proxima regeneracao completa. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
O problema
Um projeto pré-gravado criado sobre uma pasta que já continha
1_ProcessingArea_*/2_AreasOfInterest_*de um estudo anterior carregava aqueles polígonos no primeiro duplo-clique — apesar de a importação ter sido recusada no wizard.Nada levantava exceção e nada degradava visivelmente. O relatório saía completo, com todas as distâncias, ocupações de ROI e latências medidas contra a arena do experimento anterior.
Perícia do caso real que originou o PR:
config_snapshot.yaml→detection_zones.polygon: [], eimport_configcom todos os flagsfalseOs polígonos eram byte a byte idênticos ao
1_ProcessingArea_CECT_4.parquetde 13/12/2025 que estava ao lado do.mp4.Causa raiz — duas, independentes
1.
DialogManager.offer_zone_reuseimportava sob uma única condição: "este vídeo não tem zonas emproject_data?". Nunca consultava a decisão do wizard. O método foi escrito para gravações live (o recorder grava os parquets na pasta da sessão enquanto as zonas em memória ficam sob a chave do reference-frame) e a regra passou a valer para todo mundo.Agravante:
scan_input_pathsregistraparquet_filesna entrada do vídeo mesmo com a recusa, e_resolve_source_zone_parquetsvarrePath(video).parentde qualquer forma — limpar um não fecha o outro.2.
ZoneManager.save_zone_dataescreviaproject_data["detection_zones"]incondicionalmente, mesmo comvideo_pathexplícito. Esse é o default global queget_zone_data(fallback_to_global=True)serve a qualquer vídeo sem zonas próprias — então a arena do vídeo A virava a arena do vídeo B.A correção
Nova regra única:
core/services/zone_autoimport_policy.decide_zone_autoimport(), que decide por procedência, não por "achou arquivo":AUTO— silencioso, como sempre foi_wizard_metadata["import_config"]SKIPASKAUTO— legado intactoimport_zone_data_from_video_parquets()passa a importar incondicionalmente: a permissão é de quem chama. Extraíresolve_zone_parquet_candidates()para a política julgar exatamente os arquivos que serão lidos, sem janela entre a pergunta e a resposta.Fim do silêncio: o ramo
ASKabre um diálogo que nomeia a pasta de origem — era justamente a informação ausente que fazia o import parecer configuração escondida — e avisa, depois de importar, para conferir os contornos.Espelho global: vira
ZoneManager.should_mirror_to_global(). Mantido quando não há vídeo alvo (o save é o global), quandoproject_type == "live"e quando não háproject_typedeclarado. Só um projeto que positivamente se declara não-live é excluído.Segurança contra regressão nos outros fluxos
A restrição é sempre pelo caso que foi provado errado, nunca pela negação do caso conhecido:
.mp4gravados não têm chave própria emzones_by_video).ZoneContextService,ValidationManager(placeholders de sessões planejadas) eMultiAquariumCoordinator.save_manual_arenaleem o global. Preservado por teste.project_type. Caem no comportamento legado, sem alteração.Bônus: crash latente pré-existente
save_project()levantaProjectInvalidErrorsemproject_path, e o fluxo de vídeo único chegava lá depois de um import bem-sucedido, derrubando o duplo-clique. Blindado, com teste.Verificação
Testes novos cobrem cada decisão da política, cada fluxo do espelho global, e a regressão direta: "abrir o vídeo B não devolve a arena do vídeo A".
Notas de revisão
i18n — divergência deliberada do processo. As 4 strings novas foram anexadas ao
.pot/.poe o.morecompilado, em vez de rodarscripts/update_translations.py. Motivo: o catálogo emmainestá completo (0 pendências) mas defasado do fonte — pelo menos 8 msgids com_()nunca foram extraídos ("Detector models not found","Aquarium width (cm):", o aviso de escala px/cm…). O teste de integridade compara o.potcommitado com o.pocommitado, então a defasagem é invisível no CI. Regenerar arrastaria ~2600 linhas de churn alheio para dentro deste fix e obrigaria a traduzir 8 strings sem relação com ele. O par ficou registrado emlocales/_pairs/zone-autoimport-gate.jsonpara a próxima regeneração completa. Fechar a defasagem merece um PR próprio.Catálogo de mutação estendido (53 → 58, todas mortas), seguindo a regra do
MUTATION_BASELINE.md: "estenda o catálogo quando adicionar um contrato que degrada em silêncio". Este degrada — umAUTOerrado adota geometria alheia sem sinal algum.🤖 Generated with Claude Code