121 lines
12 KiB
Python
121 lines
12 KiB
Python
"""Reconcile current semantic evidence into the sole audit matrix, preserving every source and version."""
|
||
import collections
|
||
import csv
|
||
import hashlib
|
||
import json
|
||
import re
|
||
import sys
|
||
from datetime import datetime, timezone
|
||
from pathlib import Path
|
||
|
||
ROOT = Path(__file__).resolve().parent.parent
|
||
PRIVATE = ROOT / 'storage/app/private/investmoz-v2/portal-audit'
|
||
MATRIX = ROOT / 'docs/investmoz/v2/auditoria-portal/matriz-requisitos.csv'
|
||
sys.stdout.reconfigure(encoding='utf-8')
|
||
csv.field_size_limit(16 * 1024 * 1024)
|
||
http = json.loads((PRIVATE / 'opportunity-semantic-http-qa.json').read_text(encoding='utf-8-sig'))
|
||
publication = json.loads((PRIVATE / 'opportunity-semantic-publication-http-qa.json').read_text(encoding='utf-8-sig'))
|
||
verified = json.loads((PRIVATE / 'opportunity-semantic-verification-qa.json').read_text(encoding='utf-8-sig'))
|
||
environment = json.loads((PRIVATE / 'opportunity-semantic-environment-qa.json').read_text(encoding='utf-8-sig'))
|
||
active = json.loads((PRIVATE / 'dashboard-active-readonly.json').read_text(encoding='utf-8-sig'))
|
||
assert all(p['active_writes'] == 0 for p in [http, publication, verified, environment, active])
|
||
assert http['result'] == publication['result'] == 'passed'
|
||
assert http['target'] == publication['target'] == 'desk_restore_20261009_174726_test:55441'
|
||
assert sum(c['canonical_validation'] == 'passed' for c in verified['categories']) == 6
|
||
rows = list(csv.DictReader(MATRIX.open(encoding='utf-8-sig', newline='')))
|
||
before = {r['ID']: dict(r) for r in rows}
|
||
assert len(rows) == len(before) == 3386
|
||
baseline = PRIVATE / 'opportunity-semantic-matrix-before.csv'
|
||
if not baseline.exists():
|
||
baseline.write_bytes(MATRIX.read_bytes())
|
||
marker = '[Semântica QA 10/10/2026]'
|
||
|
||
|
||
def append(row, field, value):
|
||
old = re.sub(re.escape(marker) + r'.*?(?= \| \[|$)', '', row[field]).strip(' |')
|
||
row[field] = old + ' | ' + marker + ' ' + value
|
||
|
||
|
||
categories = '; '.join(f"{c['category']}#{c['id']}:{c['working_state']}" for c in verified['categories'])
|
||
evidence = ('QA18097:10rascunhos incompletos/nova sessão FortifyMFA;6contratos semânticos submetidos e revistos com gates, '
|
||
'5publicações inicialmente internas/DUAT32aprovado internamente+publicação422poratoexternopendente;4rascunhos bloqueados '
|
||
'por perfil modular não aprovado. 12erros422porcampo+rascunho inválido preservado. Estado atual:' + categories + '. '
|
||
'Investimento30:parecer29atribuído/respondido,aprovadorpendente422/outrodestinatário403/gatesnãoautoaprovados. '
|
||
'Venda33:coordenador25→publicador29/fila atribuída→outropublicador403→publicação pública sóteaser;privado preservado. '
|
||
'Provas opportunity-semantic-http-qa.json, -publication-http-qa.json e -verification-qa.json; nenhum clique observado.')
|
||
implementations = {
|
||
'AUD-OP-03': 'Workflow existente reutilizado; filas revisão/editorial/recurso/atribuídas; parecer por gate/hash/destinatário independente; coordenação editorial/reatribuição explícita. Revalida gates e responsabilidades na publicação.',
|
||
'AUD-OP-04': 'Adaptador semantic1 e OpportunityCategoryFields detalham os seis contratos do mesmo schema110defs: marcos/cenários, título/serviços/avaliação, comunidades/parcelas/benfeitorias e entidade/financeiros/governação/franquia/NDA/teaser. Fonte/hash/enums antigos preservados; não cria SQL ou identidades paralelos.',
|
||
'AUD-OP-02': 'Referências documentais aninhadas pertencem à mesma oportunidade e constam do contrato; originais cifrados/hash/scan verificados. Download/prévia usam VerifiedDocumentBinary e erro controlado; recursos excluídos não são oferecidos no editor.',
|
||
'AUD-GEO-03': 'Editor de parcela MultiPolygon com vértices/anéis/fecho explícito; limite administrativo separado. Seleção de localização e campos location_id usam nível mais profundo e mapa/contexto existente. API de seis consumidores recupera limite selecionado e dois limites de contexto; render pendente.',
|
||
'AUD-PROFILE-11': 'Validador atribuído e gates por competência; parecer técnico/jurídico exige destinatário independente com âmbito/MFA, resposta exclusiva dele, motivo/evidência e tarefa existente. Parecer não substitui aprovação do gate.',
|
||
'AUD-PROFILE-12': 'Publicação atribuível por publication_coordinator; assignee tem âmbito/MFA e é independente da autoria/aprovação. Publicador diverso403; prévia e projeção públicas do negócio usam apenas teaser autorizado.',
|
||
'AUD-PROFILE-19': 'Filas por estado/recurso/assignee e nomes de participantes elegíveis no servidor; tasks existentes recebem pareceres/atribuições/revisões/editoriais. SLAunconfigured/due_atnull, sem prazos nem titulares reais inventados.',
|
||
'AUD-PROFILE-25': 'Edidores declarativos detalhados pelo contrato do servidor, sem templates divergentes JS; erros canonical.<campo> junto aos controlos e acessibilidade; remoção do formulário núcleo duplicado no modo perfil. País/catálogo/autoridade/documentos selecionáveis; parcela/mapas reutilizados.',
|
||
'AUD-PROFILE-28': 'Contratos existentes enriquecidos e versionados sem substituir fonte/artifact110defs; rascunho admite ausência/null e grava/recupera; submissão estrita. Seis categorias demonstradas semanticamente nos cenários documentados; quatro aguardam versões modulares aprovadas.',
|
||
}
|
||
gaps = {
|
||
'AUD-OP-03': 'Visual integral/C37; incidentes/revisão periódica/agendamento; recurso e parecer externo institucionais/SLA versionado. Aprovação interna sintética não comprova atos externos.',
|
||
'AUD-OP-04': 'Visual por categoria; correspondência documental/cadastral e urbanística exige evidência/gate humano; não compara automaticamente titular e teor do PDF. Aplicabilidade de unidades/limites/materialidade ainda não expressa por regra inequívoca; Response integral/§27 permanece aberto. Quatro perfis modulares sem decisão.',
|
||
'AUD-OP-02': 'Visual de anexos/prévias; cobertura integral DOCX/XLSX/partilhas e PDFs derivados/exportações por perfil ainda sem prova web18097 específica. ClamD real por operação não extrapolado.',
|
||
'AUD-GEO-03': 'Render/seletores e mapas visualmente pendentes;393geometriasprivadas=11províncias+128distritos+254postos,188correspondênciaspendentes=31distritos+157postos no pacote581. Localidades/geometrias de parcelas não inferidas desses limites. Nomes mantidos em correspondencias-cartograficas.md.',
|
||
'AUD-PROFILE-11': 'Visual e responsabilidade institucional real; parecer externo/protocolos e disponibilidade completos. Gates sintéticos não são decisões jurídicas reais.',
|
||
'AUD-PROFILE-12': 'Visual da prévia/imagem/mapa; agendamento/tradução/revisão periódica; DUAT comatoexternopendente não publicável; perfis/regras reais pendentes.',
|
||
'AUD-PROFILE-19': 'Visual das filas e reatribuições; decisão exata:qual política versionada define prazos,tolerância,ciclos e escalamento? Quem detém as competências institucionais? Nenhum prazo/responsável real atribuído em QA.',
|
||
'AUD-PROFILE-25': 'C37 e navegação/render/erros/seletores/ciclo por conta21 ainda pendentes; código e props não demonstram cliques. Preserva nova sessão e acesso direto às ferramentas.',
|
||
'AUD-PROFILE-28': 'Quais versões imutáveis de perfil/campos/documentos/gates são aprovadas para concession,financing,supply,other? Sem inventar contratos. Visual e condições jurídicas/Response integral permanecem parciais.',
|
||
}
|
||
activation = ('Leitura ativaREADONLY10/10:020000eBalcão07/190000+08/120000instalados;030000não;040000não;'
|
||
'09/190000prepare_investor_pathausente(ledger/2colunas/definição). QA03/04/09-190instalados;'
|
||
'definição19etapaspublicada61000919-0000-4000-8000-000000000001 SHAe0acbc723cd3def7e415b2ef7a0173add08387c3cedabac9bb7b86409e4d25bf. '
|
||
'09/190000nãofoi substituída:continuanecessária paraativo e não se repeteQA. 030/040semdependênciaDDL190;'
|
||
'190dependeBalcão/journeys/steps/questionnaire_versions;workflowusa020+030+040+tasks/catalogue. '
|
||
'Planofuturo:visual/candidata→backup/restaurofrescos→códigoflagsfalse→030autenticada→040instalador/bootstrapautenticadosaindaemfalta→'
|
||
'190apenasfaltantesvia/management/balcao-install→dadosreais/perfis→cartografiaprivadaidempotente→flagspercurso/oportunidades→publicaçãoseparada. '
|
||
'0fixturesQAnoativo;readytrueépreparação;semativaçãoautorizada;reversãodesativaflags/preservahistóricoourestaurabackupverificado,nãodropusado.')
|
||
for row in rows:
|
||
key = row['ID']
|
||
if key in implementations:
|
||
append(row, 'Implementação encontrada', implementations[key])
|
||
append(row, 'Evidência funcional', evidence)
|
||
append(row, 'Lacuna', gaps[key])
|
||
append(row, 'API', evidence)
|
||
append(row, 'Gravação', 'opportunities.management_draft/workflow_context, resources/documents, tasks/audit_events existentes; hashes e histórico. Apenas QA guardada.')
|
||
append(row, 'Recuperação', '10categorias/12rascunhos inválidos recuperados noutra sessão MFA; seisoriginais permitidos21/24/25 e negados23; parecer/fila/hash/pub separados da versão privada.')
|
||
append(row, 'Interface', 'Código/build exclusivosQA preparados; campo+erro, selector+mapa, filas/pareceres/atribuições. Nenhum clique ou render observado; C37/conta18 e aceitação visual/Promotor21 pendentes.')
|
||
append(row, 'Resultado apresentado', 'RespostaHTTP/JSON e persistência demonstrados pelos casos30–39; venda33pública por teaser na prova editorial posterior. Apresentação no navegador pendente.')
|
||
append(row, 'Disponibilidade ativa', 'Lote apenasQA18097;ativoREADONLY/flagsfalse,030000/040000/09-190000nãoinstaladas,0geometrias. Sem migração/importação/grants ativos.')
|
||
if key != 'AUD-OP-02':
|
||
row['Estado'] = 'parcial'
|
||
if key in ['AUD-OPS-01', 'AUD-PROFILE-31', 'AUD-DESK-01', 'AUD-GUIDE-01']:
|
||
append(row, 'Dependência', activation)
|
||
append(row, 'Evidência funcional', 'Provasdeledger/colunas/definição:dashboard-active-readonly.json e opportunity-semantic-environment-qa.json;preservação18coleções/MFA18/manifestoativo:profiles-v1-preservation-qa.json.')
|
||
|
||
documents_path = PRIVATE / 'opportunity-semantic-documents-http-qa.json'
|
||
if documents_path.exists():
|
||
doc = json.loads(documents_path.read_text(encoding='utf-8-sig'))
|
||
if doc.get('result') == 'passed':
|
||
row = next(r for r in rows if r['ID'] == 'AUD-OP-02')
|
||
append(row, 'Consolidação', 'PDFválido1pstrictparser+ClamDreal upload/sha/download21/24/25;23download/delete403;delete21download404/removeopçãoeditor;restore+novaMFA21bytes/hashidênticos. Provaopportunity-semantic-documents-http-qa.json. Sem DOCX/XLSX/partilhaderivadainferida.')
|
||
|
||
protected = ['ID', 'Pedido/trecho', 'Fonte/ocorrências', '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)']
|
||
assert all(all(r[k] == before[r['ID']][k] for k in protected) for r in rows)
|
||
with MATRIX.open('w', encoding='utf-8-sig', newline='') as handle:
|
||
writer = csv.DictWriter(handle, fieldnames=list(rows[0])); writer.writeheader(); writer.writerows(rows)
|
||
origins = [i for r in rows for i in json.loads(r['Critérios e versões por origem (JSON)'])]
|
||
assert len(origins) == 6838
|
||
states = collections.defaultdict(lambda: {'requisitos': 0, 'linhas_origem': 0})
|
||
for r in rows:
|
||
states[r['Estado']]['requisitos'] += 1
|
||
states[r['Estado']]['linhas_origem'] += int(r['Quantidade de linhas de origem'])
|
||
counts_path = PRIVATE / 'contagens-requisitos.json'
|
||
counts = json.loads(counts_path.read_text(encoding='utf-8-sig'))
|
||
counts.update({'requisitos': len(rows), 'linhas_matriz': len(rows), 'linhas_origem': len(origins), 'por_estado': dict(states),
|
||
'semantic_updated_at': datetime.now(timezone.utc).isoformat(), 'browser_current_version_verified': False,
|
||
'conservation_sha256': hashlib.sha256(json.dumps(origins, ensure_ascii=False, sort_keys=True, separators=(',', ':')).encode()).hexdigest()})
|
||
counts_path.write_text(json.dumps(counts, ensure_ascii=False, indent=2), encoding='utf-8')
|
||
print(json.dumps({'requirements': len(rows), 'source_occurrences': len(origins), 'updated': list(implementations), 'active_writes': 0}, ensure_ascii=False))
|
||
if (PRIVATE / 'profile-installation-http-qa.json').exists():
|
||
import subprocess
|
||
subprocess.run([sys.executable, str(ROOT / 'tools/portal-audit-installation-update.py')], check=True)
|