96 lines
14 KiB
Python
96 lines
14 KiB
Python
"""Targeted updates to the existing audit; Artifact Tool performs CSV authoring."""
|
|
from pathlib import Path
|
|
from datetime import datetime, timezone
|
|
import csv, json, hashlib, collections, sys
|
|
sys.stdout.reconfigure(encoding='utf-8')
|
|
csv.field_size_limit(16000000)
|
|
ROOT=Path(__file__).resolve().parent.parent
|
|
DOC=ROOT/'docs/investmoz/v2/auditoria-portal'
|
|
PRIVATE=ROOT/'storage/app/private/investmoz-v2/portal-audit'
|
|
OUT=PRIVATE/'consumer-completion-finalize-20261010'
|
|
OUT.mkdir(exist_ok=True)
|
|
def load(name):
|
|
b=(PRIVATE/name).read_bytes()
|
|
return json.loads(b.decode('utf-16' if b.startswith(b'\xff\xfe') else 'utf-8-sig'))
|
|
def sha(file): return hashlib.sha256(file.read_bytes()).hexdigest()
|
|
gaps={
|
|
'AUD-COUNTRY-02':'Constituição FR na declaração privada de empresa QA18, residência PT e recusa de outra conta demonstradas. Restam o país nos registos mestres de organizações/representação e as variações de outros perfis; não inferir constituição da residência ou do formulário público Empresa.',
|
|
'AUD-COUNTRY-03':'País PT/origem ZA e estatística após submissão de um pedido Balcão demonstrados. Restam os outros serviços, condições/erros, encaminhamentos, mensagens/documentos e combinações da jornada/CRM; origem e página de entrada são dados distintos.',
|
|
'AUD-COUNTRY-06':'Negócio existente PT/FR submetido, recuperado noutra sessão e estatística FR incrementada uma vez; replay409 não duplica. Restam interesse no negócio público, revisão/publicação, condições/documentos/mandatos e perfis restantes.',
|
|
'AUD-COUNTRY-07':'PDF preenchido QA21 apresenta país MZ e origem PT separadamente; QA18 PT/FR mantém prova anterior. Restam submissão/conversão do apoio fundiário, estatísticas próprias e seletores/documentos por variação/perfil. O ensaio PDF não demonstra esses critérios.',
|
|
'AUD-STATS-01':'Balcão ZA0→1, Negócios FR0→1 após submissão; repetição conservou uma submissão e não duplicou estatística. Restam combinações/filtros da jornada/CRM, demais perfis, consumidores e tratamento de cancelamento/arquivo conforme contrato; conjuntos distintos não são investidores únicos.',
|
|
'AUD-GEO-01':'Projetos, clusters e corredores: quatro níveis guardados e recuperados noutra sessão; limites de contexto apresentados. Restam consumidores específicos de zonas, fundiário/Balcão e variantes de perfis/limpeza de descendentes ainda sem prova individual. Não repetir os consumidores já demonstrados.',
|
|
'AUD-GEO-02':'Quatro níveis dos três consumidores estruturais agora demonstrados. Restam173 correspondências (26 distritos/147 postos), localidades sem geometria e demais seletores; Mandie/Guro ambíguo. Preservar408 associações e fontes oficiais aceites; não atribuir limite do pai à localidade.',
|
|
'AUD-GEO-03':'Três mapas estruturais apresentam limites oficiais de contexto para a localidade selecionada, explicitando ausência de geometria própria. Restam demais mapas, tiles externos, camadas/parcelas e permissões por consumidor; cadastro, geometria e apresentação são critérios separados.',
|
|
'AUD-DOC-01':'C91/C92 PDF fundiário preenchido corrigidos e demonstrados: caracteres, país/origem, impressão sem navegação, ficheiro gerado/scan próprio e permissões privadas; originais preservados. C94 consulta autorizada em leitura e recusa de outra organização/público demonstradas; imagem original/WebP e documento do workflow têm scans próprios. Restam PDFs/exportações/partilhas/derivadas dos outros consumidores, revogação, formatos e acessibilidade documental integral.',
|
|
'AUD-OP-01':'Dashboard QA21→criação de rascunho incompleto→gravação→recuperação→correção→aprovação24→publicação25 demonstrado para investment_project. Restam outras categorias, coedição visual, arquivo/restauração conforme decisão, filtros e permissões restantes; dossier privado permanece separado.',
|
|
'AUD-OP-02':'Recursos próprios da oportunidade52: localização principal, PDF restrito e imagem sintética/WebP pelo mecanismo existente demonstrados; sem instalação nova QA. Instalação ativa030000 continua necessária se ausente. Restam lifecycle/partilhas/perfis e demais consumidores, sem duplicar originais.',
|
|
'AUD-OP-03':'Ciclo visual investment_project QA21→submete→QA24 solicita correção→QA21 corrige/resubmete→QA24 revê/aprova→QA25 publica no catálogo QA demonstrado. Restam rejeição/suspensão/alterações materiais/múltiplos papéis por interface, pareceres/coordenação/recurso e decisões SLA; provas API anteriores não substituem esses critérios.',
|
|
'AUD-OP-04':'C93 corrigiu objeto ambiental opaco reutilizando environmentalSocialCreate; rascunho incompleto e erro junto ao campo preservados. Contrato investment_project preenchido e publicado visualmente. Restam outros contratos/categorias, condições semânticas e Response/§27 integral; perfis modulares mantêm decisões separadas.',
|
|
'AUD-DESK-01':'Preparação A/B e submissão de pedido QA separado com protocolo/recuperação/replay sem duplicação demonstrados. Restam os outros serviços/condições, mensagens/transições, documentos e decisões externas aplicáveis; não repetir A/B ou C37.',
|
|
'AUD-UI-01':'A/B e C37 preservados; consumidores empresariais, estatísticas, três estruturas e ciclo investment_project demonstrados no Chromium. C94 separadores de consulta, C95 nome acessível do sector e C96 rótulo de descrição do corredor corrigidos. Restam outros consumidores/perfis, acessibilidade integral, dispositivos reais e ensaios móveis específicos deste lote.',
|
|
}
|
|
tag='[QA consumidores 10/10/2026] '
|
|
if '--verify' in sys.argv:
|
|
packet=json.loads((OUT/'csv-edits.json').read_text(encoding='utf-8'))
|
|
changed={}
|
|
for edit in packet['files']:
|
|
current=list(csv.reader((DOC/edit['filename']).open(encoding='utf-8-sig',newline='')))
|
|
old=edit['rows'];headers=current[0];assert len(current)==len(old)==3394 and headers==old[0]
|
|
patches={p['row']:p['values'] for p in edit['patches']};ids=[]
|
|
for i,(before,after)in enumerate(zip(old,current)):
|
|
for j,(a,b)in enumerate(zip(before,after)):
|
|
if a!=b:assert i in patches and headers[j] in patches[i] and b==patches[i][headers[j]]
|
|
if i in patches:
|
|
ids.append(after[headers.index('ID')])
|
|
if edit['filename'].startswith('matriz'):
|
|
key=headers.index('Critérios e versões por origem (JSON)');a=json.loads(before[key]);b=json.loads(after[key]);assert len(a)==len(b)
|
|
for previous,new in zip(a,b):assert all(new[k]==v for k,v in previous.items())
|
|
assert set(ids)==set(gaps);changed[edit['filename']]=len(ids)
|
|
rows=list(csv.DictReader((DOC/'matriz-requisitos.csv').open(encoding='utf-8-sig',newline='')))
|
|
states=dict(collections.Counter(r['Estado']for r in rows));assert states=={'parcial':3235,'não implementado':145,'dependente de decisão concreta':5,'implementado mas não ativado':1,'concluído e demonstrado':7}
|
|
scope=load('consumer-final-scope-qa.json');assert sha(ROOT/'public/build/manifest.json')==scope['active_manifest_sha256']
|
|
assert all(sha(ROOT/file)==digest for file,digest in scope['tested_sources'].items())
|
|
proof={'result':'passed','requirements':len(rows),'rows':len(rows),'source_occurrences':sum(int(r['Quantidade de linhas de origem'])for r in rows),'states':states,'changed':changed,'versions_references_and_other_cells_preserved':True,'tested_sources_current':True,'active_build_preserved':True,'active_writes':0}
|
|
(OUT/'preservation-verification.json').write_text(json.dumps(proof,ensure_ascii=False,indent=2),encoding='utf-8');print(json.dumps(proof,ensure_ascii=False));sys.exit()
|
|
proofs={mode:load(f'consumer-{mode}-chromium-20261010.json')for mode in ['entity','stats','business','structures-deep','documents','workflow','workflow-files','reviewer-files','corridor-label']}
|
|
for p in proofs.values():assert p['result']=='passed' and p['active_writes']==0 and p['target']=='desk_restore_20261009_174726_test:55441'
|
|
sql=load('consumer-final-persistence-qa.json');assert sql['result']=='passed_consumer_persistence_read_only'
|
|
scan=load('consumer-pdf-scan-qa.json');assert scan['result']=='passed_real_derivative_scan'
|
|
pdf=load('consumer-pdf-presentation-qa.json');assert pdf['result']=='passed' and pdf['sha256']==scan['derivative']['sha256']
|
|
scope=load('consumer-final-scope-qa.json');assert scope['regression_exit']==0 and scope['typescript_exit']==0
|
|
stamp=datetime.now(timezone.utc).isoformat()
|
|
notes={
|
|
'entity':'consumer-entity-chromium-20261010.json: QA18 declarou constituiçãoFR/residênciaPT, POST→v1→nova sessão→FR; QA23 não vê e não altera(404). Declaração privada, não cadastro mestre de organizações.',
|
|
'stats':'consumer-stats-chromium-20261010.json: pedido separado no dossierA, PT/ZA, PUT303→submitted/protocolo→nova sessão→gestãoZA0→1; replay200 conserva protocolo/v2. SQL:uma revisão/um evento submitted. PedidosA/B originais continuamDRAFT.',
|
|
'business':'consumer-business-chromium-20261010.json: negócio sintético existente PT/FR, C95 seletor de sector com nome próprio, oito passos→submissão200→nova sessão→submitted; replay409 sem duplicação; gestãoNegóciosFR0→1. SQL:uma transição submitted.',
|
|
'geo':'consumer-structures-deep-chromium-20261010.json: projetos/clusters/corredores CaboDelgado→ANCUABE→posto→localidade; guardarPOST200→nova sessão→quatroIDs/limites de contexto. Geometria própria da localidade ausente, mensagem explícita; sem nova importação/publicação. C96 corrigiu o rótulo de descrição do corredor, sem regravar; consumer-corridor-label-chromium-20261010.json.',
|
|
'doc':'consumer-documents-chromium-20261010.json; consumer-pdf-presentation-qa.json; consumer-pdf-scan-qa.json: C91/C92 corrigidos, PDF fundiário real gerado/inspecionado, paísMZ/origemPT, sem menus, scanClamAVclean próprio;QA23/gestor404. Três originais preservados. Documento/imagem/WebP52 têm scan próprio; C94 consulta autorizada em leitura, PDF baixado com hash exato, preview WebP renderizado, QA23 original/URLassinada403 e anónimo401; metadados privados omitidos do catálogoQA. Validador24 lê sem editar, em consumer-reviewer-files-chromium-20261010.json.',
|
|
'workflow':'consumer-workflow-chromium-20261010.json: oportunidade52 investment_project, dashboard21→draft incompleto→guardar→submeter→correção24→correção/resubmissão21→gates24/aprovação24→Publicador25 publica catálogoQA→recuperação. C93 editor ambiental corrigido. Hash aprovação/publicação igual; atores distintos, sem decisão externa. Outros contratos e critérios visuais permanecem abertos.',
|
|
}
|
|
mapping={
|
|
'AUD-COUNTRY-02':['entity'],'AUD-COUNTRY-03':['stats'],'AUD-COUNTRY-06':['business'],'AUD-COUNTRY-07':['doc'],'AUD-STATS-01':['stats','business'],
|
|
'AUD-GEO-01':['geo'],'AUD-GEO-02':['geo'],'AUD-GEO-03':['geo'],'AUD-DOC-01':['doc','workflow'],
|
|
'AUD-OP-01':['workflow'],'AUD-OP-02':['workflow','doc'],'AUD-OP-03':['workflow'],'AUD-OP-04':['workflow'],'AUD-DESK-01':['stats'],'AUD-UI-01':['entity','stats','business','geo','doc','workflow']}
|
|
files=[]
|
|
for filename in ['matriz-requisitos.csv','backlog-priorizado.csv']:
|
|
file=DOC/filename;rows=list(csv.reader(file.open(encoding='utf-8-sig',newline='')));headers=rows[0];patches=[]
|
|
for index,row in enumerate(rows[1:],1):
|
|
old=dict(zip(headers,row));key=old['ID']
|
|
if key not in gaps:continue
|
|
evidence=' | '.join(notes[name] for name in mapping[key])+' ProvaSQL:consumer-final-persistence-qa.json; QA somente, sem instalação/ativação.'
|
|
if filename.startswith('matriz'):
|
|
values={'Lacuna':tag+gaps[key]}
|
|
updates={'Evidência funcional':evidence,'Interface':evidence,'API':'Pedidos de interface e GET de recuperação no servidor18097, header QA conferido antes de escrita; ver respostas/perfis no recibo por consumidor.','Gravação':'Entidades/IDs existentes e uma fixture sintética por caso; SQLQA READONLY liga estados, versões, atores, país/origem e hashes. Sem novas tabelas, contas ou escrita ativa.','Recuperação':'Nova sessão nos consumidores descritos; SQL confirma relações, uma submissão após repetição e preservação dos critérios originaisA/B.','Resultado apresentado':evidence,'Consolidação':'Critérios específicos demonstrados; estado global e referências/versões preservados. Não repetir A/B/C37 ou outros critérios aceites.'}
|
|
values.update({field:old[field]+' | '+tag+value for field,value in updates.items()})
|
|
variants=json.loads(old['Critérios e versões por origem (JSON)'])
|
|
for variant in variants:variant['incremento_consumidores_20261010']={'at':stamp,'evidencia':evidence,'criterios_restantes':gaps[key],'active':False}
|
|
values['Critérios e versões por origem (JSON)']=json.dumps(variants,ensure_ascii=False,separators=(',',':'))
|
|
else:values={'Pendência':tag+gaps[key]+' | Histórico preservado: '+old['Pendência'],'Aceitação':old['Aceitação']+' | '+tag+evidence,'Correção/componentes':old['Correção/componentes']+' | '+tag+'Reutilizar entidades/contratos/catálogo/geometria/scan existentes. C91/C92 impressão, C93 definição ambiental, C94 consulta em leitura, C95 nome do sector, C96 descrição do corredor. Executar somente critérios restantes, sem reinstalar QA.'}
|
|
patches.append({'row':index,'values':values})
|
|
assert len(patches)==len(gaps)
|
|
files.append({'filename':filename,'input_sha256':sha(file),'rows':rows,'patches':patches,'allowed_columns':sorted({k for p in patches for k in p['values']})})
|
|
(OUT/'csv-edits.json').write_text(json.dumps({'files':files},ensure_ascii=False),encoding='utf-8')
|
|
(OUT/'criterion-evidence.json').write_text(json.dumps({'at':stamp,'notes':notes,'remaining':gaps,'scope':scope,'pdf':pdf,'sql':sql},ensure_ascii=False,indent=2),encoding='utf-8')
|
|
print(json.dumps({'prepared':len(gaps),'same_matrix':True,'csv_authored':False,'active_writes':0}))
|