240 lines
24 KiB
Python
240 lines
24 KiB
Python
"""Prepare additions to the SAME registries; preserve all prior criteria and states."""
|
||
from pathlib import Path
|
||
from datetime import datetime, timezone
|
||
from collections import Counter, defaultdict
|
||
from zipfile import ZipFile
|
||
import csv, hashlib, json, re, xml.etree.ElementTree as ET
|
||
|
||
csv.field_size_limit(20_000_000)
|
||
root = Path(__file__).resolve().parent.parent
|
||
private = root/'storage/app/private/investmoz-v2/portal-audit'
|
||
directory = private/'segmentation-reconciliation-20261011'
|
||
docs = root/'docs/investmoz/v2/auditoria-portal'
|
||
extraction = json.loads((directory/'extraction.json').read_text(encoding='utf-8'))
|
||
provenance = json.loads((directory/'source-provenance.json').read_text(encoding='utf-8'))
|
||
sha = lambda data: hashlib.sha256(data).hexdigest()
|
||
digest = provenance['sha256']
|
||
tag = '[Segmentação '+digest[:12]+' — exemplar v1 local; identidade v1(3) pendente] '
|
||
matrix_rows = list(csv.reader((docs/'matriz-requisitos.csv').open(encoding='utf-8-sig', newline='')))
|
||
headers = matrix_rows[0]
|
||
existing = {r[headers.index('ID')]: dict(zip(headers, r)) for r in matrix_rows[1:]}
|
||
|
||
new = {
|
||
'AUD-MARKET-01': ('Negócios similares por área, raio, sector e classificação, com fonte, data e cobertura', 'não implementado', 'INV-004',
|
||
'Reutilizar ExistingBusiness/objetos e geo_locations/geo_geometries para estabelecimentos comprovados. Separar estabelecimento observado de anúncio de transação; pesquisa espacial explicada, sem transformar artigo ou licença em oportunidade.',
|
||
'Sem consumidor de negócios similares por raio/classificação demonstrado. O marketplace de negócios não comprova um cadastro de estabelecimentos.',
|
||
'Pesquisa combina raio/sector/classificação; apresenta fonte/data/cobertura/limitações; recupera os mesmos registos; não mostra dados privados nem locais sem correspondência.'),
|
||
'AUD-ESTABLISHMENT-01': ('Importar empresas licenciadas e empreendimentos turísticos com normalização, correspondências e rejeições, sem duplicações', 'não implementado', 'INV-008',
|
||
'Inventariar os ficheiros fornecidos, inspecionar colunas e IDs reais; reutilizar organizações/estabelecimentos existentes por correspondência, serviços de importação privada/hash e hierarquia territorial. Não criar organizações automaticamente com base em nome ou licença.',
|
||
'Importador específico destes cadastros e reconciliação de entidades/versões ainda por preparar. Importação de artigos do legado e geometrias não substitui esta carga.',
|
||
'Prévia sem escrita; casos inequívocos/casos ambíguos/rejeições por linha; aplicação QA com ator autorizado; repetição sem duplicações; fonte/data/história e recuperação; nenhuma georreferenciação inventada.'),
|
||
'AUD-FUND-01': ('Catálogo de fundos nacionais e territoriais com elegibilidade explicada e atualização', 'não implementado', 'INV-005',
|
||
'Reutilizar biblioteca e cadastro de entidades/parceiros para fontes e gestores reais; regras versionadas de cobertura/elegibilidade com indeterminado quando faltar parâmetro. Correspondência não representa aprovação ou compromisso de financiamento.',
|
||
'Correspondência financeira genérica RF-CLU-005 é relacionada, mas não implementa o catálogo de fundos com cobertura e janelas. Não foram encontrados modelo/controlador/consumidor específicos no inventário de código.',
|
||
'Fonte, gestor, sectores, cobertura nacional ou territorial, datas/estado e documentos; filtro e explicação por critério; fundos expirados sinalizados; guardar interesse autorizado sem duplicação.'),
|
||
'AUD-COMPANY-01': ('Comparador de forma societária orientado por perguntas, com fundamento, alternativas e limites', 'não implementado', 'SOC-001',
|
||
'Reutilizar organização mestre, perfil/dossier e biblioteca. Comparador informativo por número de participantes, responsabilidade, gestão, capital e atividade; separar empresário individual, sociedade, representação e cooperação.',
|
||
'Seleção de forma jurídica no roteiro não comprova comparador orientado por perguntas. Termos e pressupostos da secção9 devem ser reconciliados com o exemplar legal aplicável antes de se tornarem regras.',
|
||
'Perguntas recuperáveis, alternativas explicadas com fonte/data/limites, correção dos dados, retorno ao dossier sem alterar retrospectivamente a entidade ou garantir decisão externa.'),
|
||
'AUD-COMPANY-02': ('Modelos versionados de estatutos e contratos sociais por forma jurídica, com finalidade e limitações', 'não implementado', 'SOC-002',
|
||
'Reutilizar documents/portal_resources/revisions e links, sem outro repositório; conservar original, hash, origem, versão, tipo, finalidade e adaptação separada.',
|
||
'A secção6.1 afirma disponibilidade ativa parcial, mas não identifica exemplares e URLs. A biblioteca genérica não comprova estes modelos; não marcar ativos sem localizar os ficheiros e verificar recuperação/permissões.',
|
||
'Exemplar/URL exatos por modelo, original recuperável, versão e limitações visíveis, formulário contextual e derivada própria; nova versão conserva submissões/modelos anteriores.'),
|
||
'AUD-CAPITAL-01': ('Orientação sobre private equity e capitalização, preparação, riscos, documentos e percurso', 'não implementado', 'CAP-001',
|
||
'Reutilizar biblioteca/guia público, dossier e parceiros autorizados; manter separado de ofertas experimentais. RF-CRM-009 e regras de facilitação são relações existentes, sem equivalência automática com este guia.',
|
||
'Não foi demonstrado guia estruturado de capitalização. O pedido não autoriza custódia, intermediação, promessa de retorno ou aprovação dos módulos de oferta.',
|
||
'Consulta pública com fontes/versões, instrumentos distinguidos, riscos/documentos e próxima ação; guardar referência no dossier correto; sem recomendar sucesso nem assumir compromisso.'),
|
||
'AUD-CAPITAL-02': ('Guião de admissão à Bolsa de Valores de Moçambique, distinguindo Bolsa, regulador e operadores', 'não implementado', 'CAP-002',
|
||
'Reutilizar guia/biblioteca/dossier/checklists. Relacionar pedido de admissão e patrocínio às referências específicas; separar ação interna do portal e ato de entidade competente.',
|
||
'Guião por segmento, documentos, condições e fonte aplicável ainda não demonstrado. Não inferir valores, prazos, prospectos, taxas ou aprovação pelos artigos1–3.',
|
||
'Etapas, entidade, base/artigo/redação, documentos, requisitos por segmento e lacunas explícitas; recuperação no dossier e encaminhamento permitido, sem integração externa simulada.'),
|
||
'AUD-CHECKLIST-01': ('Editor de checklists e formulários configuráveis, publicação de versões e preservação de submissões anteriores', 'parcial', 'INV-007',
|
||
'Reutilizar config/document-requirements.php, requisitos/formulários/documentos/tarefas e serviços do Balcão existentes. Não duplicar motor, tabelas ou as cinco fases/19 etapas.',
|
||
'Checklists e catálogos configurados existentes; editor sem alterar código/publicação versionada por entidade não demonstrados. Separar obrigações comerciais, registo/publicidade e licença setorial.',
|
||
'Gestor autorizado configura/previsualiza/publica versão, investidor recebe versão aplicável ao contexto/data, guarda incompleto e recupera; nova versão não modifica submissão anterior nem trata decisão interna como licença.'),
|
||
}
|
||
assert not (set(new)&set(existing)), 'New ID already belongs to the canonical matrix'
|
||
aliases = {
|
||
'INV-001':['MASTER-1f0a4cde-0001','AUD-DESK-02'], 'INV-002':['AUD-DESK-01','AUD-CRM-01'],
|
||
'INV-003':['AUD-SIM-01','AUD-SIM-02'], 'INV-004':['AUD-MARKET-01'], 'INV-005':['AUD-FUND-01'],
|
||
'INV-006':['AUD-OP-03','AUD-PROFILE-28'], 'INV-007':['AUD-CHECKLIST-01','MASTER-9dee08d0-0049'],
|
||
'INV-008':['AUD-ESTABLISHMENT-01'], 'INV-009':['AUD-PROFILE-11','AUD-PROFILE-19','AUD-PROFILE-21'],
|
||
'INV-010':['MASTER-e1169a77-0018'], 'SOC-001':['AUD-COMPANY-01'], 'SOC-002':['AUD-COMPANY-02'],
|
||
'CAP-001':['AUD-CAPITAL-01'], 'CAP-002':['AUD-CAPITAL-02'],
|
||
}
|
||
all_ids = set(existing)|set(new)
|
||
assert all(rid in all_ids for ids in aliases.values() for rid in ids)
|
||
|
||
def section_ids(key):
|
||
exact = {
|
||
'1.2':aliases['INV-001']+aliases['INV-007'], '1.3':aliases['INV-003'],
|
||
'1.4':aliases['INV-006']+aliases['CAP-001']+aliases['CAP-002'],
|
||
'1.5':aliases['INV-004']+aliases['INV-005']+aliases['INV-008'],
|
||
'1.6':aliases['INV-007']+aliases['INV-009'], '1.7':aliases['INV-007']+aliases['INV-009'],
|
||
'3.1':['AUD-PROFILE-01','AUD-PROFILE-09','AUD-PROFILE-12'],
|
||
'3.1.1':['AUD-PROFILE-06','AUD-PROFILE-07','AUD-PROFILE-17','AUD-PROFILE-24'],
|
||
'3.2':aliases['INV-001']+aliases['INV-002'], '3.2.1':['AUD-PROFILE-08','V2-12','AUD-DESK-02'],
|
||
'3.2.2':['AUD-OP-02','AUD-CHECKLIST-01','AUD-PROFILE-25'],
|
||
'3.2.3':['AUD-OP-04','AUD-MARKET-01','AUD-FUND-01'],
|
||
'3.3':aliases['INV-006'], '3.3.1':['AUD-OP-02','AUD-OP-03','AUD-PROFILE-28'],
|
||
'4.3':['AUD-GEO-03','AUD-ESTABLISHMENT-01','RF-CRM-001'],
|
||
'4.4':['AUD-CHECKLIST-01','AUD-PROFILE-01','AUD-OPS-03'],
|
||
'5.2':['AUD-DESK-02','V2-12','AUD-PROFILE-19'], '5.3':['AUD-OP-02','AUD-OPS-01'],
|
||
'6.2':['AUD-OP-02','AUD-CHECKLIST-01','AUD-PROFILE-08'],
|
||
'6.3':['AUD-OP-03','AUD-PROFILE-01','AUD-LIB-01'], '6.3.1':['AUD-CHECKLIST-01','AUD-LIB-01'],
|
||
'7.1':['AUD-PROFILE-26','RF-CRM-011'], '7.2':['AUD-LIB-02','AUD-SCAN-01'],
|
||
'7.3':['AUD-LIB-01','AUD-SIM-02','AUD-ESTABLISHMENT-01'],
|
||
'8.1':['AUD-ESTABLISHMENT-01','AUD-LIB-02'], '8.2':['AUD-UI-01','AUD-PROFILE-30','AUD-OPS-03'],
|
||
'8.4':['AUD-PROFILE-20','AUD-PROFILE-22'], '8.6':aliases['INV-010'],
|
||
'9.3':aliases['SOC-002'],
|
||
}
|
||
if key in exact:return exact[key]
|
||
if key.startswith(('1.0','1.8')):return ['AUD-CHECKLIST-01','AUD-LIB-01','AUD-SIM-02']
|
||
if key.startswith('9'):return aliases['SOC-001']+aliases['SOC-002']
|
||
if key.startswith('4'):return ['AUD-OPS-03','AUD-GEO-03']
|
||
if key.startswith('5'):return ['AUD-DESK-02','AUD-SIM-02','AUD-OP-03']
|
||
if key.startswith('7'):return ['AUD-PROFILE-23','AUD-PROFILE-26','AUD-OPS-01']
|
||
if key.startswith('2.1'):return ['AUD-UI-01','AUD-OPS-03','MASTER-e1169a77-0018']
|
||
return ['AUD-OPS-01','AUD-OPS-03']
|
||
|
||
# Table cells are grouped by semantic row. Header rows and section headings are not requests.
|
||
paragraphs = extraction['paragraphs']
|
||
tables = defaultdict(list)
|
||
for p in paragraphs:
|
||
if 'table' in p:tables[(p['table'],p['row'])].append(p)
|
||
blocks = []
|
||
used_tables = set()
|
||
for p in paragraphs:
|
||
if 'table' in p:
|
||
key = (p['table'],p['row'])
|
||
if key in used_tables:continue
|
||
used_tables.add(key)
|
||
if p['row']==1:continue
|
||
members = tables[key]
|
||
text = ' | '.join(n['text'] for n in members if n['text'])
|
||
ids = sorted({rid for alias in re.findall(r'\b(?:INV-\d{3}|SOC-\d{3}|CAP-\d{3})\b',text) for rid in aliases.get(alias,[])})
|
||
ids = ids or section_ids(p['section'])
|
||
else:
|
||
if not p['text'] or p['style'].startswith(('Ttulo','Heading','Title','Subtitle')):continue
|
||
members=[p]; text=p['text'];ids=section_ids(p['section'])
|
||
assert all(rid in all_ids for rid in ids)
|
||
blocks.append({'id':'SEG-'+digest[:8]+'-p'+str(members[0]['paragraph']), 'section':p['section'],
|
||
'first_paragraph':members[0]['paragraph'],'last_paragraph':members[-1]['paragraph'],
|
||
'text':text,'requirements':sorted(set(ids)), 'source_sha256':digest,
|
||
'locator':'word/document.xml#p'+str(members[0]['paragraph']),
|
||
'declared_document_state_is_not_functional_evidence':True,
|
||
'exact_requested_exemplar_confirmed':False})
|
||
|
||
by_id=defaultdict(list)
|
||
for block in blocks:
|
||
for rid in block['requirements']:by_id[rid].append(block)
|
||
correspondence=[]
|
||
for section in extraction['sections']:
|
||
scoped=[b for b in blocks if b['section']==section['key']]
|
||
ids=sorted({rid for b in scoped for rid in b['requirements']})
|
||
if not ids:ids=section_ids(section['key'])
|
||
correspondence.append({'section':section['key'],'heading':section['heading'], 'paragraphs':section['paragraphs'],
|
||
'requirements':ids,'states':{rid:existing[rid]['Estado'] if rid in existing else new[rid][1] for rid in ids},
|
||
'disposition':{rid:'reutilizado/complementado' if rid in existing else 'acrescentado sem correspondência direta' for rid in ids},
|
||
'next_action':{rid:existing[rid]['Lacuna'] if rid in existing else new[rid][4] for rid in ids},
|
||
'requested_exemplar_confirmation_pending':True})
|
||
|
||
# The legal fields are statements attributed to this specification, never activated law.
|
||
legal=[]
|
||
for start in range(338,406,4):
|
||
ps=paragraphs[start-1:start+3]
|
||
if len(ps)!=4:continue
|
||
legal.append({'reference':ps[0]['text'],'locator':ps[0]['locator'],'source_sha256':digest,
|
||
'access_declared_by_document':ps[1]['text'],'amendments_repeals_declared_by_document':ps[2]['text'],
|
||
'applicable_redaction_declared_by_document':ps[3]['text'],
|
||
'urls':sorted({link['url'] for p in ps for link in p['hyperlinks'] if link['url']}),
|
||
'linked_requirements':section_ids('1.8.4'),'official_acceptance_preserved':True,
|
||
'portal_applicability_evidence_not_inferred':True,'rule_enabled':False})
|
||
legal.append({'reference':'Verificação técnica adicional de três URLs, 11/10/2026',
|
||
'access':'PDF CMVM no Banco de Moçambique recuperado pela ferramenta web (28 páginas); INTIC BR229 indisponível via ferramenta e BVM timeout.',
|
||
'amendments_repeals':'Não foi realizada pesquisa exaustiva nova; conservar a atribuição às declarações do exemplar.',
|
||
'applicable_redaction':'Nenhuma redação foi adotada no motor; artigo/âmbito/período continuam por regra concreta.',
|
||
'official_acceptance_preserved':True,'rule_enabled':False})
|
||
|
||
packet={'proof_filename':'csv-authoring-proof.json','files':[]}
|
||
for name in ['matriz-requisitos.csv','backlog-priorizado.csv','inventario-fontes.csv']:
|
||
raw=(docs/name).read_bytes()
|
||
rows=list(csv.reader((docs/name).open(encoding='utf-8-sig',newline='')));hs=rows[0];patches=[];append=[]
|
||
if name=='matriz-requisitos.csv':
|
||
for i,row in enumerate(rows[1:],1):
|
||
rid=row[hs.index('ID')]
|
||
if rid not in by_id:continue
|
||
before=dict(zip(hs,row));assert tag not in before['Fonte/ocorrências']
|
||
origin=json.loads(before['Critérios e versões por origem (JSON)'] or '[]')
|
||
assert isinstance(origin,list)
|
||
references=[{'id':b['id'],'source':provenance['source'],'sha256':digest,'section':b['section'],
|
||
'locator':b['locator'],'first_paragraph':b['first_paragraph'],'last_paragraph':b['last_paragraph'],
|
||
'text':b['text'],'document_state_not_acceptance':True,'requested_exemplar_identity_pending':True} for b in by_id[rid]]
|
||
values={'Fonte/ocorrências':before['Fonte/ocorrências']+'\n'+tag+'; '.join(b['id']+' §'+b['section'] for b in by_id[rid]),
|
||
'IDs das linhas de origem':before['IDs das linhas de origem']+'; '+'; '.join(b['id'] for b in by_id[rid]),
|
||
'Quantidade de linhas de origem':str(int(before['Quantidade de linhas de origem'])+len(references)),
|
||
'Critérios e versões por origem (JSON)':json.dumps(origin+references,ensure_ascii=False),
|
||
'Versões divergentes':before['Versões divergentes']+'\n'+tag+'Fases0–4 do §2 são releases de desenvolvimento, não as cinco fases/19 etapas. Agrupamentos §3.3.1 não convertem categorias. Papéis genéricos §3.1 não concedem permissão. §6 contém estados documentais, não prova do ambiente atual. §7p811 não reabre credibilidade das fontes oficiais. SLA/ofertas/competências continuam decisões separadas. Disponibilidade do guia/modelos só por URL/exemplar verificado.',
|
||
'Consolidação':before['Consolidação']+'\n'+tag+'Pedidos relacionados sem novo inventário/backlog paralelo; referência documental acrescentada, critérios anteriores preservados. Confirmar os bytes do v1(3) antes do fecho documental.'}
|
||
if rid in ['AUD-LIB-01','AUD-LIB-02']:
|
||
proof=json.loads((directory/'library-replay-qa.json').read_text(encoding='utf-8'))
|
||
values['Evidência funcional']=before['Evidência funcional']+'\n'+tag+'Prévia→importação privada QA por gestor17→ClamD real→repetição sem duplicar→SHA/bytes originais recuperados. Recurso29/documento589ed9ac-967e-439e-9dde-b0fb3996e546. Chromium gestão/download autorizado e nova sessão; Promotor403 e público404. Recibos library-preview/apply/replay-qa.json e segmentation-library-chromium-20261011.json. Não publicar ou ativar regras.'
|
||
patches.append({'row':i,'values':values})
|
||
for rid,(request,state,alias,reuse,gap,acceptance) in new.items():
|
||
refs=by_id[rid]
|
||
values={h:'' for h in hs}
|
||
values.update({'ID':rid,'Pedido/trecho':request,'Fonte/ocorrências':tag+'Alias documental '+alias+'; '+'; '.join(b['id'] for b in refs),
|
||
'Módulo':alias+' / incremento posterior ao workflow das seis categorias',
|
||
'Rotas':'Consumidor específico ainda por delimitar; reutilizar rotas de biblioteca/Balcão/negócios/simulador pertinentes, sem atribuir existência fictícia.',
|
||
'Implementação encontrada':reuse,'Disponibilidade ativa':'Não demonstrada para este requisito específico; nenhum pacote ativado por esta incorporação. COUNTRY02ON preservado.',
|
||
'Evidência funcional':'Leitura integral do exemplar local e inventário código/documental; importação/retrieval deste DOCX não comprova a funcionalidade descrita.',
|
||
'Estado':state,'Lacuna':gap,'Dependência':'Confirmar exemplar solicitado v1(3). '+('Identificar fontes/dados/âmbito/período concretos; não reabrir aceitação oficial.' if rid not in ['AUD-CAPITAL-01','AUD-CHECKLIST-01'] else 'Reutilizar componentes/contratos; definição funcional própria e nenhuma nova ativação.'),
|
||
'Critério de aceitação':acceptance,'Prioridade':'P2','Versões divergentes':'Código '+alias+' é alias deste documento, não renumeração do roteiro/backlog. Conteúdo/regras pendentes não se tornam aprovados pela importação.',
|
||
'IDs das linhas de origem':'; '.join(b['id'] for b in refs),'Quantidade de linhas de origem':str(len(refs)),
|
||
'Critérios e versões por origem (JSON)':json.dumps(refs,ensure_ascii=False),
|
||
'Interface':'Consumidor específico não demonstrado.','API':'Contrato específico não demonstrado.',
|
||
'Gravação':'Nenhuma entidade/regra criada para este módulo.','Recuperação':'A recuperação do original da especificação é prova do acervo, não deste percurso.',
|
||
'Resultado apresentado':'Pendente de implementação/prova própria.','Consolidação':'Novo ID apenas após pesquisa de correspondência: os requisitos relacionados são componentes a reutilizar, não equivalências. '+tag})
|
||
append.append([values[h] for h in hs])
|
||
elif name=='backlog-priorizado.csv':
|
||
for i,row in enumerate(rows[1:],1):
|
||
rid=row[hs.index('ID')]
|
||
if rid not in by_id:continue
|
||
old=dict(zip(hs,row))
|
||
patches.append({'row':i,'values':{'Correção/componentes':old['Correção/componentes']+'\n'+tag+'§'+', §'.join(sorted({b['section'] for b in by_id[rid]}))+'; referência e critérios complementares na matriz existente, sem repetir provas.',
|
||
'Dependência/pergunta':old['Dependência/pergunta']+'\n'+tag+'Confirmar identidade do exemplar solicitado. Não reabrir A/B/C37/COUNTRY02; ofertas/competências/SLA e restauro proposto continuam separados.'}})
|
||
for rid,(request,state,alias,reuse,gap,acceptance) in new.items():
|
||
data={'Prioridade':'P2','ID':rid,'Estado':state,'Pendência':gap,'Correção/componentes':reuse,
|
||
'Dependência/pergunta':'Exemplar v1(3) ainda não confirmado; fontes/dados/atos concretos próprios. Nenhuma nova regra ou divulgação inferida.',
|
||
'Aceitação':acceptance,'Ordem do próximo lote':'Incremento separado do workflow base; '+alias+' é alias documental. '+tag}
|
||
append.append([data[h] for h in hs])
|
||
else:
|
||
known={(r[hs.index('Exemplar/caminho')],r[hs.index('SHA256')]) for r in rows[1:]}
|
||
for item in provenance['source_directory_inventory']:
|
||
if (item['path'],item['sha256']) in known:continue
|
||
data={'Exemplar/caminho':item['path'],'Bytes':str(item['bytes']),'SHA256':item['sha256'],'Tipo':Path(item['path']).suffix,
|
||
'Nota de leitura':('Original integral907parágrafos/14tabelas; relações externas/notas/alterações verificadas; hash e cópia privada preservados. Identidade v1(3) pendente.' if item['sha256']==digest else 'Inventariado integralmente no mesmo diretório antes da seleção; conteúdo não promovido a implementação. '+item.get('read_error','')),
|
||
'Disponibilidade':'PrivadoQA/biblioteca recurso29' if item['sha256']==digest else 'Origem acessível; divulgação não inferida.',
|
||
'Uso em cálculo':'Nenhum cálculo ativado.','Situação jurídica/documental':'Documento de programação; não estender aceitação oficial. Original/histórico preservados.',
|
||
'Edição/versão identificada':'v1; propriedadeOOXMLrevision15; modificado11/10/2026 12:29UTC; v1(3) pendente.' if item['sha256']==digest else 'Nome/hash do exemplar; versão substantiva não inferida.'}
|
||
append.append([data[h] for h in hs])
|
||
for attachment in item['attachments']:
|
||
data={h:'' for h in hs};data.update({'Exemplar/caminho':item['path']+'::'+attachment['part'],'Bytes':str(attachment['bytes']),'SHA256':attachment['sha256'],'Tipo':'anexo incorporado','Nota de leitura':'Anexo inventariado sem descarte; origem no DOCX indicado.','Disponibilidade':'Parte do original, divulgação não inferida.','Uso em cálculo':'Nenhum cálculo ativado.','Situação jurídica/documental':'Não inferida.','Edição/versão identificada':item['sha256']})
|
||
if (data['Exemplar/caminho'],data['SHA256']) not in known:append.append([data[h] for h in hs])
|
||
packet['files'].append({'filename':name,'input_sha256':sha(raw),'rows':rows,'patches':patches,'append':append,
|
||
'source_hash_updates':[],'allowed_columns':sorted({k for p in patches for k in p['values']})})
|
||
|
||
assert len(blocks)==len({b['id'] for b in blocks})
|
||
assert sum(len(v) for v in by_id.values())>=len(blocks)
|
||
states=Counter(x['Estado'] for x in existing.values())+Counter(v[1] for v in new.values())
|
||
counts={'old_requirements':len(existing),'new_requirements':len(new),'requirements':len(existing)+len(new),
|
||
'matrix_rows':len(existing)+len(new),'source_occurrences_before':sum(int(x['Quantidade de linhas de origem']) for x in existing.values()),
|
||
'added_source_occurrences':sum(len(v) for v in by_id.values()),'source_request_blocks':len(blocks),
|
||
'sections':len(correspondence),'states':dict(states),'reused_requirement_IDs':sorted(set(by_id)-set(new)),
|
||
'new_IDs':sorted(new),'no_old_state_changed':True,'closed_AB_C37_preserved':True,'active_writes':0}
|
||
counts['source_occurrences']=counts['source_occurrences_before']+counts['added_source_occurrences']
|
||
for name,value in [('csv-edits.json',packet),('correspondence.json',correspondence),('request-blocks.json',blocks),
|
||
('document-aliases.json',aliases),('legal-reference-links.json',legal),('counts.json',counts)]:
|
||
(directory/name).write_text(json.dumps(value,ensure_ascii=False,indent=2),encoding='utf-8')
|
||
print(json.dumps({k:counts[k] for k in ['old_requirements','new_requirements','requirements','source_request_blocks','sections','added_source_occurrences','active_writes']},ensure_ascii=False))
|