"""Reconcile this increment into the existing matrix, preserving source records.""" import collections import csv import hashlib import json import re from pathlib import Path root = Path(__file__).resolve().parent.parent docs = root / 'docs/investmoz/v2/auditoria-portal' private = root / 'storage/app/private/investmoz-v2/portal-audit' path = docs / 'matriz-requisitos.csv' baseline = private / 'matriz-pre-dashboard-20261010.csv' if not baseline.exists(): baseline.write_bytes(path.read_bytes()) with path.open(encoding='utf-8-sig', newline='') as stream: reader = csv.DictReader(stream) fields = reader.fieldnames rows = list(reader) source_fields = ['ID', 'Pedido/trecho', 'Fonte/ocorrências', 'Módulo', 'Critério de aceitação', 'Versões divergentes', 'IDs das linhas de origem', 'Quantidade de linhas de origem', 'Critérios e versões por origem (JSON)', 'Consolidação'] def source_digest(records): return hashlib.sha256(json.dumps([{key: r[key] for key in source_fields} for r in records], ensure_ascii=False, sort_keys=True).encode()).hexdigest() baseline_rows = list(csv.DictReader(baseline.open(encoding='utf-8-sig', newline=''))) before_digest = source_digest(baseline_rows) assert source_digest(rows) == before_digest and len(rows) == 3354 assert sum(int(r['Quantidade de linhas de origem']) for r in rows) == 5830 marker = ' | [QA dashboard 10/10/2026] ' def update(id, changes): row = next(r for r in rows if r['ID'] == id) for key, value in changes.items(): row[key] = row[key].split(marker)[0] + marker + value common = 'dashboard-opportunities-http-qa.json: dashboard→destinos anunciados→create→201→GET→logout/nova sessão→GET→PUT→arquivo→filtro archived→restauro; QA13 mantém ID,4 eventos. dashboard-manager-mfa-http-qa.json: gestor17 MFA em duas sessões/reload/logout,0 registos alterados. 12 oportunidades anteriores, relações/documentos/dossiers preservados por SHA;6 publicadas mantidas. browser_visual_observed=false.' active = 'Leitura SQL REPEATABLE READ READ ONLY10/10/2026: investemoz:5432;020000 instalada,030000 ausente;6 oportunidades publicadas. Contas2/13 autorizadas e verificadas. Manifesto ativo75352185c2a85c34ab726613db864a598e301a5b342ebbc80ac8e678eed17a94 preservado;flag do incremento false. Perfil/URL da sessão reportada ainda não confirmados.' update('AUD-OP-01', { 'Implementação encontrada': 'OpportunityManagement existente; archive explícito no estado archived distinto de delete/deleted_at, com ETag/razão/confirmação/auditoria/restauro. Flag QA; nenhum schema/cadastro paralelo.', 'Disponibilidade ativa': active, 'Evidência funcional': common, 'API': '201 criação/replay;200 GET/PUT/lifecycle;422 sem confirmação;409 conflito;403 outro perfil. Nova ação archive só com configuração do incremento.', 'Gravação': 'QA13: draft→edição→archived(deleted_at=null)→draft;4 eventos,ID/histórico preservados,0 publicação.', 'Recuperação': 'GET por ID e filtro archived; nova sessão do gestor14 e gestor17/MFA recuperam QA13.', 'Interface': 'Código/assets QA em build-qa-opportunities; HTML da página usa manifesto QA. Dashboard/sidebar recebem Gestão de Oportunidades/Nova oportunidade. Sem clique/renderização de navegador demonstrados.', 'Resultado apresentado': 'Mensagens/controlos preparados;props devolvem título editado/archived/draft. Resultado visual pendente.', 'Lacuna': 'C43: navegação e archive corrigidos em QA; fecho exige dashboard→cliques→recuperação/edição/arquivo/restauro renderizados. Cadastro modular integral/workflow ainda pendentes.', 'Dependência': 'Perfil exato da sessão reportada; navegador QA e futura ativação separada. Não repetir020000 nem exigir030000 para rascunho básico.'}) update('AUD-DASH-01', { 'Implementação encontrada': 'C43: AppSidebar omitia todos os filhos exceto primeiro destino de cada grupo; Painel apontava/investidor também para gestor; criação no fim com labelCriar oportunidade. Atalhos/dashboard/menu agrupado corrigidos reutilizando props autorizadas; Painel da gestão→/dashboard,dossiers→/investidor. FlagQA e build separado; área privada preservada.', 'Disponibilidade ativa': active, 'Evidência funcional': common, 'Interface': 'AppSidebar+dashboard corrigidos; validação visual do layout/atalhos/teclado/móvel pendente. API e HTML não equivalem a clique.', 'API': 'Links descobertos da resposta de /dashboard e seguidos no servidor18097. GestoresQA14/17 autorizados;QA13/18 não são gestores; investidor/unverified/guest não ganham gestão.', 'Gravação': 'Percurso de oportunidade QA13 conserva dossier privado e seis versões públicas; testes de negativa preservam dados.', 'Recuperação': 'Nova sessão/reload do gestor17 com MFA mantém oportunidade13; outra conta403.', 'Resultado apresentado': 'Manifesto ativo preservado; HTML QA aponta build-qa-opportunities e version hash próprio. Perfil da sessão do relato ainda desconhecido; sem atribuição de causa a essa conta.', 'Lacuna': 'C43 corrigido em QA/backend/assets, sem ativação; interface e identidade da sessão reportada pendentes. C37/A-B e demais ações do dashboard continuam abertos.', 'Dependência': 'Ensaio visual autenticado em18097. Gestor requer email em portal.managers+verificado; não elevar investidor18 por causa da ausência do menu.'}) update('AUD-UI-01', {'Evidência funcional': common, 'Lacuna': 'Navegador automático sem apps/browsers; C43 tem protocolo/persistência/assets demonstrados, visual pendente. C37 mantém pendência anterior sem novo login do investidor18. Roteiro curto de gestão com perfil17 em demonstracao-dashboard-oportunidades.md.', 'Interface': 'C43/gestão e C37/investidor são ensaios independentes; ambos exigem navegador, não foram encerrados por HTTP.'}) update('AUD-OP-04', { 'Implementação encontrada': 'OpportunityRealEstateDraft valida campos fornecidos nas24 propriedades realEstatePatch do schema normativo. OpportunityManagement guarda/merge no management_draft existente, sem coluna/tabela nova. Flag especializada só QA. Primeira parte de um contrato, não9contratos completos.', 'Evidência funcional': 'real-estate-draft-http-qa.json: QA14(diferente de user14),201/replay mesmoID→GET→nova sessão→patch merge→cinco422 sem mutação→outra conta403;originais/dossiers preservados. OpportunityRealEstateDraftTest: versão publicada intacta e sem dados especializados na API pública.', 'API': 'real_estate parcial no POST drafts/PUT draft existente, com tipos/enums/limites/UUID e campos de resposta recusados. Condições finais/Create/Response/referências canónicas NÃO estão completas.', 'Gravação': 'QA14: contrato parcial imobiliário em management_draft; PATCH preserva valores anteriores; nenhum estado submetido/aprovado/publicado.', 'Recuperação': 'GET edit devolve values.real_estate depois de logout/nova sessão; outro perfil403.', 'Interface': 'Formulário especializado ainda ausente; nenhuma prova visual ou contrato integral atribuído ao adaptador API.', 'Resultado apresentado': 'Campos privados recuperados por props; ainda não há apresentação/edição por controlos especializados.', 'Lacuna': 'Completar formulário, vínculos canónicos/documentais, condições finais §26,Create/Response e demais módulos DUAT/transação. Dados parciais guardáveis não validam viabilidade nem decisão externa.', 'Dependência': 'Reutilizar documentos/direitos/objetos/negócios/territórios existentes; não renumerar bigint para exemplosUUID. Sem nova decisão para implementar emQA.'}) next(r for r in rows if r['ID'] == 'AUD-OP-04')['Estado'] = 'parcial' update('AUD-OP-03', {'Lacuna': 'Workflow completo permanece não implementado. Primeira gravação privada imobiliáriaC44 não é revisão/aprovação/publicação. Próximo: gate de completude por versão, autor/revisor distintos, devolver com razão, revisão técnica/jurídica/territorial, aprovação/publicação separadas; preservar versão pública.'}) update('AUD-OPS-01', {'Evidência funcional': 'dashboard-active-readonly.json reconfirma020000 instalada e030000 ausente,0 escrita ativa;build QA separado,manifesto ativo preservado. Fingerprints incluem dashboard/sidebar/middleware/config/schema/adaptador; evidência anterior ready não certifica esta revisão.', 'Lacuna': 'Revisão candidata não fixada: C37/C43/A-B/consumidores visuais pendentes. Renovar backup/restauro depois desses ensaios. Ativação de atalhos/arquivo e adaptador especializado separada das instalações030000/190000/importação1→2→3/publicação.'}) update('AUD-DOC-01', {'Evidência funcional': 'Mesma matriz3354requisitos/5830ocorrências preserva fontes/trechos/IDs/versões por SHA; apenasAUD-OP-04 muda não implementado→parcial pelo incremento específicoAPI. Lista integral de3349abertos derivada desta matriz,sem estados independentes nem segunda matriz.'}) assert source_digest(rows) == before_digest old_states = {r['ID']: r['Estado'] for r in baseline_rows} state_changes = [r['ID'] for r in rows if r['Estado'] != old_states[r['ID']]] assert state_changes == ['AUD-OP-04'] with path.open('w', encoding='utf-8', newline='') as stream: writer = csv.DictWriter(stream, fieldnames=fields) writer.writeheader() writer.writerows(rows) states = collections.Counter(r['Estado'] for r in rows) counts = {'requisitos': len(rows), 'linhas_matriz': len(rows), 'linhas_origem': sum(int(r['Quantidade de linhas de origem']) for r in rows), 'por_estado': {state: {'requisitos': n, 'linhas_origem': sum(int(r['Quantidade de linhas de origem']) for r in rows if r['Estado'] == state)} for state, n in states.items()}, 'conservation_sha256': before_digest, 'metodo': 'Mesma matriz canónica; requisitos e ocorrências separados; alteração específica de estadoAUD-OP-04 documentada; agrupamento editorial não altera estado.', 'browser_current_version_verified': False} (private / 'contagens-requisitos.json').write_text(json.dumps(counts, indent=2, ensure_ascii=False), encoding='utf-8') (private / 'dashboard-matrix-reconciliation-qa.json').write_text(json.dumps({**counts, 'state_changes': state_changes, 'sources_versions_preserved': True, 'active_writes': 0}, indent=2, ensure_ascii=False), encoding='utf-8') print(json.dumps(counts, ensure_ascii=False))