"""Reconcile existing named gaps with authenticated QA proof; retain earlier names/counts.""" import hashlib import json from pathlib import Path root = Path(__file__).resolve().parent.parent private = root / 'storage/app/private/investmoz-v2/portal-audit' docs = root / 'docs/investmoz/v2/auditoria-portal' proof = json.loads((private / 'geometry-crosswalk-http-qa.json').read_text(encoding='utf-8-sig')) preservation = json.loads((private / 'geometry-crosswalk-preservation-qa.json').read_text(encoding='utf-8-sig')) consumer_http = json.loads((private / 'geometry-crosswalk-consumers-http-qa.json').read_text(encoding='utf-8-sig')) assert proof['applied'] and not proof['published'] and not proof['interface_observed'] and proof['active_writes'] == 0 assert preservation['target'] == proof['target'] and preservation['catalogue_preserved'] and preservation['original_geometries_preserved'] == 361 assert consumer_http['target'] == proof['target'] and not consumer_http['applied'] and not consumer_http['interface_observed'] assert len(consumer_http['consumer_recovery']) == len(consumer_http['public_recovery']) == 32 plans = {p['level']: p for p in proof['plans']} names = {1: 'Províncias', 2: 'Distritos', 3: 'Postos administrativos'} gaps, explicit = [], [] for level in [1, 2, 3]: path = private / f'moz-adm{level}-ine-20190607.geojson' assert hashlib.sha256(path.read_bytes()).hexdigest() == plans[level]['source_sha256'] source = json.loads(path.read_text(encoding='utf-8-sig')) rows = {r['external_id'].rsplit(':', 1)[1]: r for r in plans[level]['rows']} for feature in source['features']: p = feature['properties'] r = rows[p[f'ADM{level}_PCODE']] if r.get('correspondence') and r['status'] in ['insert', 'reuse']: explicit.append({'pcode': p[f'ADM{level}_PCODE'], 'source_name': r['name'], 'id': r['location_id'], **r['correspondence']}) if r['status'] in ['insert', 'reuse']: continue reason = {'parent_unresolved': 'Pai do exemplar sem correspondência; não associa por nome isolado', 'name_unresolved': 'Nome sem correspondência única sob o pai resolvido', 'ambiguous_name': 'Mais de um cadastro com mesmo nome/pai; ambos preservados', 'invalid_geometry': 'Geometria inválida; original sem reparação silenciosa'}.get(r['status'], r['status']) gaps.append({'level': level, 'name': r['name'], 'pcode': p[f'ADM{level}_PCODE'], 'parent_name': p.get(f'ADM{level-1}_PT'), 'parent_pcode': p.get(f'ADM{level-1}_PCODE'), 'province': p['ADM1_PT'], 'reason': reason, 'status': r['status'], 'candidates': r['candidates']}) resolved = sum(p['resolved'] for p in plans.values()) expected = sum(p['expected'] for p in plans.values()) assert expected - resolved == len(gaps) and preservation['total_active'] == resolved text = ['# Correspondências cartográficas — estado atual e histórico', '', '10/10/2026. Complemento de AUD-GEO-02/03 na mesma matriz. QA18097/desk_restore_20261009_174726_test:55441. Esperado refere o exemplar INE/UNOCHA de07/06/2019 aceite, não uma divisão administrativa atual certificada.', '', '| Nível | Esperado | Resolvido QA | Pendente | Incremento sobre09/10 |', '| --- | ---: | ---: | ---: | ---: |'] for level in [1, 2, 3]: p = plans[level] text.append(f"| {names[level]} | {p['expected']} | {p['resolved']} | {p['pending']} | +{p['resolved'] - {1:9,2:120,3:232}[level]} |") text += ['', f'**{resolved} resolvidos/{len(gaps)} pendentes, de {expected} elementos.** As 32 novas geometrias são 2+8+22; as 361 anteriores e os 1990 registos territoriais foram preservados no retrato. O seletor recupera 1920 referências de quatro níveis:11+148+437+1324; os 70 países do cadastro não integram esse denominador. Sem geometrias de localidades neste exemplar. Cadastro, geometria autorizada e apresentação funcional são critérios independentes.', '', '## Correspondências explícitas', '', '| INE | Nome do exemplar | Cadastro/código | ID existente | Fundamento |', '| --- | --- | --- | --- | --- |'] for r in explicit: text.append(f"| {r['pcode']} | {r['source_name']} | {r['target_name']}/{r['target_source_code']} | {r['id']} | {r['basis']} |") text += ['', 'Configuração versionada config/geometry-correspondences.php; fonte/hash/edição/nome/código/pai entram na prévia assinada. CódigosINE e workbook não são convertidos aritmeticamente. Zero renomeações, novos territórios, mudanças de pai ou publicação. Cidade De Maputo/MZ0601 é agregado de2019: não se associa a um dos distritos urbanos cadastrados.', '', '## Mandie — ambiguidade preservada', '', 'MZ040402/Mandie sob Guro tem candidatos4944a651-23ec-5d51-a5ff-14b0b53ed672 (source_code02) e ea7be18f-2a93-5416-a07a-c207a700e070 (03). Workbook aceite:MANDIE nas linhas11117 e11138, códigos diferentes. Sufixo do P-code não prova equivalência de códigos. Conferir correspondência documental específica; não fundir cadastros. Não bloqueia correspondentes únicos independentes.', ''] for level in [1, 2, 3]: items = [r for r in gaps if r['level'] == level] text += [f'## {names[level]} — {len(items)} pendentes atuais', '', '| Código | Nome exato | Pai | Causa |', '| --- | --- | --- | --- |'] for r in items: text.append(f"| {r['pcode']} | {r['name']} | {r['parent_name'] or '—'} | {r['reason']} |") text.append('') text += ['## Provas e critérios abertos', '', '- geometry-crosswalk-http-qa.json: aplicação autenticada/MFA, replay e API do mapa para22postos novos. A tentativa anterior aplicou2províncias+8distritos antes de parar na ambiguidadeMandie; recibos/eventos originais estão na prova de preservação. O tratamento foi corrigido para manter ambiguidades pendentes e importar as correspondências únicas.', '- geometry-crosswalk-preservation-qa.json: alvo guardado,393 internos,32novos,361anteriores/1990cadastros preservados. geometry-consumers-path-qa.json recupera393/393 por serviço READ ONLY e0limites privados públicos. Não é prova de publicação ou desenho.', '- geometry-crosswalk-consumers-http-qa.json: nova consulta dos 32 limites inseridos, pelo endpoint usado pelo componente, recupera 32 para gestor/MFA e nenhum para público; as duas províncias Maputo têm correspondência única por nome exato. Não aplicou outro lote e não demonstra renderização.', '- TerritoryFields e ProvinceSelect partilhados usam os mesmos IDs/endpoints/preview. ProvinceSelect exige nome exato/único; conserva texto histórico e explica ausência. TypeScript/build passam; seleção/gravação/recuperação e Leaflet por consumidor exigem interface.', '- Reversão recusa alteração/publicação/FK e descendentes ativos da edição. Ordem:postos→distritos→províncias, só lotes novos; originais/cadastros/histórico preservados. Identidades revertidas não são ressuscitadas por replay.', ''] destination = docs / 'correspondencias-cartograficas.md' old = destination.read_text(encoding='utf-8-sig') marker = '## Histórico09/10 — nomes e contagens anteriores' history = old.split(marker, 1)[1].strip() if marker in old else old.strip() destination.write_text('\n'.join(text) + '\n' + marker + '\n\n' + history + '\n', encoding='utf-8') named = private / 'geometry-named-gaps.json' archive = private / 'geometry-named-gaps-20261009.json' if named.exists() and not archive.exists(): archive.write_bytes(named.read_bytes()) named.write_text(json.dumps(gaps, ensure_ascii=False, indent=2), encoding='utf-8') print(json.dumps({'expected': expected, 'resolved': resolved, 'pending': len(gaps), 'named_gaps': 'correspondencias-cartograficas.md', 'active_writes': 0}))