126 lines
17 KiB
Python
126 lines
17 KiB
Python
"""A read-only, searchable view of every open requirement in the existing CSV."""
|
||
import collections
|
||
import csv
|
||
import hashlib
|
||
import html
|
||
import json
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
sys.stdout.reconfigure(encoding='utf-8')
|
||
csv.field_size_limit(16 * 1024 * 1024)
|
||
root = Path(__file__).resolve().parent.parent
|
||
docs = root / 'docs/investmoz/v2/auditoria-portal'
|
||
private = root / 'storage/app/private/investmoz-v2/portal-audit'
|
||
matrix = docs / 'matriz-requisitos.csv'
|
||
rows = list(csv.DictReader(matrix.open(encoding='utf-8-sig', newline='')))
|
||
opened = [r for r in rows if r['Estado'] != 'concluído e demonstrado']
|
||
prepared = {'AUD-COUNTRY-01', 'AUD-COUNTRY-02', 'AUD-COUNTRY-03', 'AUD-COUNTRY-04', 'AUD-COUNTRY-06', 'AUD-COUNTRY-07', 'AUD-STATS-01',
|
||
'AUD-GEO-01', 'AUD-OP-01', 'AUD-OP-02', 'AUD-OP-05', 'AUD-DESK-01', 'AUD-DESK-02', 'AUD-CRM-01', 'AUD-GUIDE-01', 'AUD-SIM-01', 'AUD-PART-01', 'AUD-LAND-01', 'AUD-LIB-02'}
|
||
# C37 QA18 is manually closed; QA21 dashboard access is separately demonstrated.
|
||
# Keep functional gaps under their canonical state instead of relabelling them as access failures.
|
||
access_failure = set()
|
||
prepared.update({'AUD-DASH-01', 'AUD-UI-01', 'AUD-GEO-02', 'AUD-GEO-03', 'AUD-SIM-02'})
|
||
prepared.update({'AUD-OP-03','AUD-OP-04','AUD-PROFILE-19'})
|
||
prepared.update({'AUD-LEG-01', 'AUD-LEG-02', 'AUD-LEG-03', 'AUD-LEG-05', 'AUD-LEG-06', 'AUD-LEG-07'})
|
||
prepared.update(f'AUD-PROFILE-{number:02d}' for number in [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,23,24,25,30,31,32])
|
||
groups = ['Não implementados', 'Parciais', 'Preparados em QA', 'Instalados com falha de acesso']
|
||
|
||
|
||
def group(row):
|
||
if row['Estado'] == 'não implementado':
|
||
return groups[0]
|
||
if row['ID'] in access_failure:
|
||
return groups[3]
|
||
if row['ID'] in prepared or row['Estado'] == 'implementado mas não ativado':
|
||
return groups[2]
|
||
return groups[1]
|
||
|
||
|
||
next_actions = {
|
||
'AUD-DASH-01': 'Atalhos, rascunho, gravação, recuperação, edição, pedido de arquivo e arquivo independente QA21/25 já demonstrados; não repetir. Completar política e ação de restauração pelo perfil competente, sem conceder arquivo direto ao Promotor por inferência. Restantes categorias/workflow exigem provas próprias.',
|
||
'AUD-UI-01': 'C37 encerrado exclusivamente para autenticação QA18 por confirmação manual, com provas anteriores preservadas. Não repetir o ciclo concluído. Completar A/B móvel, critérios especializados e restantes consumidores/perfis; auditoria e ativação permanecem abertas.',
|
||
'AUD-OP-01': 'Executar C43 a partir do dashboard com Promotor QA21/MFA; conferir gravação, recuperação, coedição autorizada, pedidos/decisões de arquivo e restauro pelos perfis competentes, sem confundir o dossier privado com o catálogo público.',
|
||
'AUD-OP-02': 'Conferir operações visuais por tipo/perfil e scans próprios de derivadas; após aceitação/backuprestaurado, aplicar autenticadamente030000 apenas se ausente. Não repetir020000.',
|
||
'AUD-OP-03': 'Executar ciclo visual com PromotorQA21, Validador24, Publicador25 e recurso29; API/persistência do ciclo já demonstradas. Completar pareceres/incidentes/coordenação editorial e política SLA/agenda sem inventar responsáveis ou prazo.',
|
||
'AUD-OP-04': 'Completar editores/validações semânticas §25 e Response integral; seis contratos estruturais aceites em QA não encerram todos os gates. Quatro categorias conservam drafts, mas exigem perfil modular aprovado para submissão/publicação.',
|
||
'AUD-OP-05': 'Selecionar/guardar/reabrir7/8 na interface com versão do catálogo explícita; conferir legado6sem converter significados e fases de revisão/publicação.',
|
||
'AUD-GEO-01': 'Pesquisar cada província, guardar/reabrirID/nome e confirmar o limite no seletor partilhado para cada perfil.',
|
||
'AUD-GEO-02': 'Resolver as 173 correspondências restantes (26 distritos/147 postos), por pai, edição e identidade inequívoca; Mandie/Guro mantém-se ambíguo. Preservar as 408 associações demonstradas, aplicar privadamente em QA com replay e testar cada seletor.',
|
||
'AUD-GEO-03': 'Testar cada mapa/consumidor com os 408 limites privados e permissões; distinguir território, geometria e apresentação. Permanecem 26 distritos/147 postos e polígonos de localidades. Publicação das camadas separada.',
|
||
'AUD-REL-01': 'Ligar as relações existentes a citações e fonte/edição específicas; não inferir pares oportunidade–projeto/cluster nem criar ligações sem evidência.',
|
||
'AUD-REL-02': 'Pergunta concreta:quais são os pares identificados e documentados entre as seis oportunidades existentes e âncoras/clusters? Preservar relações atuais enquanto indeterminados.',
|
||
'AUD-SIM-02': 'Completar correspondência diploma/artigo/âmbito/período/parâmetro nas fontes oficiais aceites; testar cenários rastreáveis. Taxa ausente permanece indeterminada; importação não ativa cálculo.',
|
||
'AUD-LIB-01': 'Completar consulta do acervo/autorização por ficheiro a partir do inventário253ficheiros+60anexos; preservar original/hash/versão e estados consulta,jurídico e cálculo independentes.',
|
||
'AUD-LIB-02': 'Completar aplicação por mecanismo autenticado, prévia/hash/duplicação/conflito/escrita privada/scan; CLIapply continuaQA-only. Conferir partilha/revogação/tipos na interface.',
|
||
'AUD-SCAN-01': 'Verificar scan próprio de cada operação/derivada restante e arranque/reboot/assinaturas ClamD/GD no servidor de futura ativação; provaClamD real existente mantida.',
|
||
'AUD-DESK-01': 'Percorrer os três serviços e seus anexos/estados por perfil; guardar/reabrir campos/país/origem e mostrar resultado/pendências/recibo.',
|
||
'AUD-DESK-02': 'A/B internos concluídos em QA, com preparação/recuperação do pedido e C88. Restam outros vínculos empresariais/legados e conferência documental por consumidor; não repetir A/B/C37.',
|
||
'AUD-CRM-01': 'Tarefa paralela S02/S10 A/B demonstrada. Restam conversão sem duplicar investidor, responsável/próxima ação e atribuições/filas por perfil; sem dependência de CRM externo.',
|
||
'AUD-GUIDE-01': 'Conferir cada recomendação contra âmbito/período/elementos da fonte oficial aceite; exibir fundamento/próxima ação sem score de aprovação inventado.',
|
||
'AUD-SIM-01': 'A/B contextuais já guardados/recuperados com resultado no dossier correto e permissões. Não repetir; verificar variantes anónimas/não contextuais, expiração/purga e consumidores restantes.',
|
||
'AUD-PART-01': 'Completar rascunho/candidatura/sala/marcos por perfis, partilha/revogação e downloads/renderização na interface, preservando originais e consentimentos.',
|
||
'AUD-LAND-01': 'Percorrer formulário fundiário/país/origem/localização/anexo/partilha com gravação e nova sessão; vincular direitos existentes sem venda automática deDUAT.',
|
||
'AUD-BIZ-01': 'Completar negócio existente com direitos/mandatos/documentos e geografia/país, revisar/publicar por ação própria e confirmar recuperação/perfis na interface.',
|
||
'AUD-EDU-01': 'Retomar somente após as falhas prioritárias:reutilizar atores/cadastros e demonstrar procura/capacidade/investimento com dados reais e permissões.',
|
||
'AUD-EDU-02': 'Depois deI0/C43/contratos prioritários, permitir guardar procura/capacidade incompletas como rascunho privado nas entidades existentes, sem exigir os campos finais.',
|
||
'AUD-EDU-03': 'Não iniciar agora; depois das prioridades, jornadas/painéis por ator reutilizando19etapas/5fases e contas existentes, sem renumerar roteiros.',
|
||
'AUD-OPS-01': 'Concluir critérios visuais→fixar candidata→renovar backup/restauro→disponibilizar código/flags→instalar somente migrations ausentes→percurso/importação/publicação separadas, com reversão preservadora.',
|
||
'AUD-LEG-01': 'C71 recuperado; 18/50 versões cadastradas (17 com texto, uma vazia privada). Reconciliar as restantes 32 versões, paginação/sitemap e anexos/C68; prévia, hash, scan real, replay e divulgação separada.',
|
||
'AUD-LEG-02': 'Desktop dos setores demonstrado no Chromium; completar móvel/acessibilidade sistemática e demais perfis. Não inferir aceitação integral a partir de tipografia/imagens carregadas.',
|
||
'AUD-LEG-03': 'Quatro dossiers setoriais QA21 demonstrados na interface, nova sessão e retorno. A/B internos e C37 QA18 têm fechos próprios preservados; não repetir. Completar os restantes consumidores, conteúdos/variantes setoriais, móvel/acessibilidade e perfis, sem extrapolar a cobertura do legado.',
|
||
'AUD-LEG-05': 'Preservar Energia e Mineração distintas; Turismo editorial sem associação geográfica automática. Fundamentar relações territoriais/por etapa com citação específica, reutilizando a mesma fonte da biblioteca.',
|
||
'AUD-LEG-06': 'URLs e retornos dos setores demonstrados; completar o restante mapa de migração/aliases e cobertura do acervo. C37/QA18 e A/B mantêm critérios independentes.',
|
||
'AUD-LEG-07': '22 originais/hash/ClamD/replay demonstrados; 21 divulgados separadamente e Mercado Financeiro vazio privado. Completar as 32 versões restantes, C68, links/anexos, móvel/acessibilidade e visual do editor Assunto opcional C74.',
|
||
'AUD-DOC-01': 'Reconciliar individualmente cláusulas/versões com prova datada; conservar os estados históricos e atualizar somente o critério demonstrado na matriz única.',
|
||
'AUD-UX-01': 'Percorrer interface/teclado/móvel e remover somente informação sem utilidade demonstrada, preservandoIDs/URLs e rastreabilidade.',
|
||
'AUD-HUMAN-01': 'Pergunta concreta:qual é a entidade operadora, contacto oficial e texto/versão dos termos a apresentar? Não atribuir a entidade/responsável inexistente.',
|
||
'AUD-HUMAN-02': 'Pergunta concreta:quais URLs, serviços e responsáveis institucionais devem ser associados? Percursos internos continuam independentes destas integrações.',
|
||
'AUD-HUMAN-03': 'Pergunta concreta:qual modelo multicritério, indicadores ebaseline/pilotos estão aprovados? Até haver definição, apresentar evidências/cenários sem score de sucesso.'}
|
||
for id in ['AUD-COUNTRY-01', 'AUD-COUNTRY-02', 'AUD-COUNTRY-03', 'AUD-COUNTRY-04', 'AUD-COUNTRY-06', 'AUD-COUNTRY-07', 'AUD-STATS-01']:
|
||
next_actions[id] = next(r['Lacuna'].split(' | Histórico preservado')[0] for r in rows if r['ID'] == id)
|
||
|
||
# The current matrix lists only remaining criteria; do not re-run accepted A/B,
|
||
# dashboard access or the visual investment_project approval/publication cycle.
|
||
for id in ['AUD-GEO-01', 'AUD-GEO-02', 'AUD-GEO-03', 'AUD-DOC-01',
|
||
'AUD-OP-01', 'AUD-OP-02', 'AUD-OP-03', 'AUD-OP-04',
|
||
'AUD-DESK-01', 'AUD-UI-01']:
|
||
next_actions[id] = next(r['Lacuna'] for r in rows if r['ID'] == id)
|
||
|
||
|
||
def action(row):
|
||
if row['ID'].startswith('AUD-PROFILE-'):
|
||
return row['Lacuna'] + ' Ver plano e roteiro em perfis-v1-reconciliacao.md; continuar QA sem afetar ativo.'
|
||
if row['ID'] in next_actions:
|
||
return next_actions[row['ID']]
|
||
if row['ID'].startswith('AUD-FIELD-'):
|
||
return 'Executar neste consumidor específico:pesquisar→selecionar→guardar→reabrir→apresentar no campo/mapa; trocar pai→limpar filhos; verificar autorização. Corrigir falha reproduzida usando os componentes territoriais existentes.'
|
||
return 'Conferir e completar emQA o critério específico abaixo nas rotas/entidades existentes, preservandoIDs e versões; registar interface/API/gravação/recuperação/resultados separados. Correspondência ainda não provada exige localizar a implementação antes de atribuir conclusão.'
|
||
|
||
|
||
escape = html.escape
|
||
counts = collections.Counter(group(r) for r in opened)
|
||
for label in groups:
|
||
counts.setdefault(label, 0)
|
||
cards = []
|
||
for r in sorted(opened, key=lambda x: (groups.index(group(x)), x['Prioridade'], x['Módulo'], x['ID'])):
|
||
body = ''.join('<dt>' + escape(label) + '</dt><dd>' + escape(r[key]) + '</dd>' for label, key in [('Pedido', 'Pedido/trecho'), ('O que falta', 'Lacuna'), ('Rotas', 'Rotas'), ('Componentes encontrados', 'Implementação encontrada')])
|
||
cards.append('<article data-group="'+escape(group(r))+'" data-module="'+escape(r['Módulo'])+'"><h2>'+escape(r['ID'])+' · '+escape(r['Módulo'])+'</h2><p>'+escape(group(r))+' · '+escape(r['Estado'])+' · '+escape(r['Prioridade'])+'</p><dl>'+body+'<dt>Próxima ação</dt><dd>'+escape(action(r))+'</dd><dt>Critério de aceitação</dt><dd>'+escape(r['Critério de aceitação'])+'</dd></dl><details><summary>Fontes, versões e evidência do requisito</summary><dl>'+''.join('<dt>'+escape(k)+'</dt><dd>'+escape(r[k])+'</dd>' for k in ['Fonte/ocorrências', 'IDs das linhas de origem', 'Quantidade de linhas de origem', 'Versões divergentes', 'Critérios e versões por origem (JSON)', 'Disponibilidade ativa', 'Evidência funcional', 'Interface', 'API', 'Gravação', 'Recuperação', 'Resultado apresentado'])+'</dl></details></article>')
|
||
summary = '; '.join(g+': '+str(counts[g]) for g in groups)
|
||
page = '''<!doctype html><html lang="pt"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>InvestMoz — pedidos abertos</title><style>body{font:16px/1.55 system-ui;margin:0;background:#f5f7fa;color:#172332}main{max-width:1050px;margin:auto;padding:24px}header{position:sticky;top:0;background:#f5f7fa;padding:12px 0;border-bottom:1px solid #9aa}input,select{font:inherit;padding:9px;max-width:100%;margin:4px}article{background:white;border:1px solid #ccd5df;border-radius:8px;padding:20px;margin:18px 0}h1{font-size:1.7rem}h2{font-size:1.15rem}dt{font-weight:650;margin-top:12px}dd{margin-left:0;white-space:pre-wrap;overflow-wrap:anywhere}details{margin-top:16px}article[hidden]{display:none}</style><main><h1>Pedidos anteriores ainda abertos</h1><p>Vista de leitura gerada exclusivamente da matriz-requisitos.csv. Não é outra matriz nem permite editar estados. 3.354 requisitos/linhas;5.830 ocorrências;5 concluídos de âmbito restrito;3.349 abertos.</p><p>''' + escape(summary) + '''</p><p>O agrupamento indica a frente de trabalho; mantém o estado canónico em cada cartão. “Preparados em QA” identifica provas de escopos concretos e conserva as lacunas do requisito inteiro. “Instalados com falha de acesso” reúneC43/C37 ainda abertos, com correções/backendQA e aceitação visual pendente; não confirma o perfil da sessão reportada. As4decisões concretas permanecem emParciais com o seu estado original.</p><header><label>Pesquisar <input id="q" type="search" placeholder="ID, pedido, campo ou fonte"></label><label>Grupo <select id="g"><option value="">Todos</option>''' + ''.join('<option>'+escape(g)+'</option>' for g in groups) + '''</select></label><label>Módulo <select id="m"><option value="">Todos</option>''' + ''.join('<option>'+escape(m)+'</option>' for m in sorted({r['Módulo'] for r in opened})) + '''</select></label><p id="result" role="status">3.349 requisitos apresentados</p></header>''' + ''.join(cards) + '''</main><script>const cards=[...document.querySelectorAll('article')];const q=document.querySelector('#q'),g=document.querySelector('#g'),m=document.querySelector('#m');const search=cards.map(c=>c.textContent.toLocaleLowerCase('pt'));function filter(){const term=q.value.toLocaleLowerCase('pt');let n=0;cards.forEach((c,i)=>{c.hidden=!!((g.value&&c.dataset.group!==g.value)||(m.value&&c.dataset.module!==m.value)||(term&&!search[i].includes(term)));if(!c.hidden)n++;});document.querySelector('#result').textContent=n+' requisitos apresentados';}[q,g,m].forEach(c=>c.addEventListener('input',filter));</script></html>'''
|
||
page = page.replace('3.354 requisitos/linhas;5.830 ocorrências;5 concluídos de âmbito restrito;3.349 abertos.',
|
||
f'{len(rows):,} requisitos/linhas;{sum(int(r["Quantidade de linhas de origem"]) for r in rows):,} ocorrências;{len(rows)-len(opened)} concluídos de âmbito restrito;{len(opened):,} abertos.')
|
||
page = page.replace('3.349 requisitos apresentados', f'{len(opened):,} requisitos apresentados')
|
||
page = page.replace('“Instalados com falha de acesso” reúneC43/C37 ainda abertos, com correções/backendQA e aceitação visual pendente; não confirma o perfil da sessão reportada.',
|
||
'“Instalados com falha de acesso” contém apenas casos de acesso ainda demonstradamente abertos. C37 foi encerrado para autenticação QA18; o acesso ao dashboard QA21 tem prova própria. Os outros critérios funcionais continuam abertos e a disponibilidade ativa é separada.')
|
||
decision_count = sum(r['Estado'] == 'dependente de decisão concreta' for r in opened)
|
||
page = page.replace('As4decisões concretas', f'As{decision_count}decisões concretas')
|
||
(private / 'pedidos-abertos.html').write_text(page, encoding='utf-8')
|
||
proof = {'matrix_sha256': hashlib.sha256(matrix.read_bytes()).hexdigest(), 'canonical_requirements': len(rows), 'source_occurrences': sum(int(r['Quantidade de linhas de origem']) for r in rows),
|
||
'open_requirements': len(opened), 'group_counts': dict(counts), 'decision_requirements': [r['ID'] for r in opened if r['Estado'] == 'dependente de decisão concreta'],
|
||
'open_ids': [r['ID'] for r in opened], 'one_card_per_open_id': len(cards) == len(opened) == len({r['ID'] for r in opened}),
|
||
'report_sha256': hashlib.sha256(page.encode()).hexdigest(), 'qa_authenticated_url': 'http://127.0.0.1:18097/qa-auditoria/pedidos-abertos'}
|
||
assert proof['one_card_per_open_id'] and sum(counts.values()) == len(opened)
|
||
(private / 'pedidos-abertos-projection.json').write_text(json.dumps(proof, indent=2, ensure_ascii=False), encoding='utf-8')
|
||
print(json.dumps({k: v for k, v in proof.items() if k != 'open_ids'}, ensure_ascii=False))
|