Files
Investidor-V2/tools/portal-audit-dashboard-update.py
nala 01405cbf8a
tests / ci (push) Canceled after 0s
oportunidades 5
2026-10-10 14:15:28 +02:00

94 lines
10 KiB
Python

"""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))