94 lines
10 KiB
Python
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))
|