"""Reconcile the observed graph and prepare a stable library import, never publish.""" import csv import hashlib import json import re import shutil import xml.etree.ElementTree as ET from pathlib import Path ROOT = Path(__file__).resolve().parents[1] PRIVATE = ROOT / 'storage/app/private/investmoz-v2/portal-audit/legacy-discovery' DOCS = ROOT / 'docs/investmoz/v2/auditoria-portal' STAGE = ROOT / 'storage/app/private/document-import-staging/legacy-agronegocio' STAGE.mkdir(parents=True, exist_ok=True) inventory = json.loads((PRIVATE / 'inventory.json').read_text(encoding='utf-8')) rows = inventory['records'] article = next(r for r in rows if r['url'] == 'https://investemoz.tempo.co.mz/agronegocio') post, seo = article['public_props']['post'], article['public_props']['seo'] image = next(r for r in rows if r['url'] == post['thumbnail']) assert article['status'] == image['status'] == 200 and image['media_type'] == 'image/png' content = {'format': 'investmoz-legacy-article-v1', 'source_url': article['url'], 'legacy_id': post['id'], 'slug': post['slug'], 'title': post['title'], 'author': post['author'], 'legacy_category': post['category'], 'published_at': seo['publishedTime'], 'modified_at': seo['modifiedTime'], 'content_html': post['content'], 'image_source_url': image['url'], 'image_sha256': image['sha256'], 'references': re.findall(r'href=[\"\']([^\"\']+)', post['content'])} (STAGE / 'agronegocio-source.txt').write_text(json.dumps(content, ensure_ascii=False, indent=2), encoding='utf-8') shutil.copyfile(ROOT / image['private_file'], STAGE / 'Agronegocio.png') manifest = {'documents': [ {'external_id': 'legacy-tempo-post-171985-20260420', 'relative_path': 'legacy-agronegocio/agronegocio-source.txt', 'file_name': 'agronegocio-source.txt', 'title': 'Agronegocio', 'issuer': post['author'], 'document_date': seo['publishedTime'][:10], 'version_label': 'Legado modificado ' + seo['modifiedTime'], 'source_url': article['url'], 'source_reference': 'Tempo; post 171985; categoria original Oportunidades; artigo educativo. Publicado ' + seo['publishedTime'] + '; modificado ' + seo['modifiedTime'], 'category': 'education', 'topic': 'agronegocio', 'document_type': 'guide', 'language': 'pt', 'description': 'Corredores de desenvolvimento do agronegócio em Moçambique. Conteúdo editorial do legado; não constitui uma oportunidade concreta nem decisão externa.'}, {'external_id': 'legacy-tempo-agronegocio-image-' + image['sha256'][:16], 'relative_path': 'legacy-agronegocio/Agronegocio.png', 'file_name': 'Agronegocio.png', 'title': 'Imagem Agronegocio — legado Tempo', 'issuer': 'Tempo (alojamento; autoria individual não indicada)', 'version_label': image['sha256'][:32], 'source_url': image['url'], 'source_reference': 'Imagem ligada ao post 171985. Autoria individual e data de criação não indicadas; não inferidas.', 'category': 'media', 'topic': 'agronegocio', 'document_type': 'reference_data', 'language': 'pt', 'description': 'Imagem original do artigo Agronegocio; manter o original sem alterações.'} ]} (STAGE / 'manifest.json').write_text(json.dumps(manifest, ensure_ascii=False, indent=2), encoding='utf-8') with (DOCS / 'legado-inventario.csv').open('w', encoding='utf-8-sig', newline='') as f: fields = ['URL', 'HTTP', 'Tipo', 'Título', 'ID legado', 'Autor', 'Categoria legado', 'Publicado', 'Modificado', 'Bytes', 'SHA256', 'Destino proposto', 'Situação'] w = csv.DictWriter(f, fieldnames=fields) w.writeheader() for r in rows: p = r.get('public_props', {}).get('post', {}) s = r.get('public_props', {}).get('seo', {}) w.writerow(dict(zip(fields, [r['url'], r.get('status'), r.get('media_type'), p.get('title', r.get('title')), p.get('id'), p.get('author'), p.get('category'), s.get('publishedTime'), s.get('modifiedTime'), r.get('bytes'), r.get('sha256'), '/agronegocio' if r['url'] == article['url'] else 'biblioteca / correspondência por rever', 'piloto preparado' if r['url'] in [article['url'], image['url']] else 'inventariado; não importado']))) posts = {} for r in rows: p = r.get('public_props', {}).get('post') if p: s = r.get('public_props', {}).get('seo', {}) key = str(p['id']) + '|' + str(s.get('modifiedTime')) + '|' + hashlib.sha256(p.get('content', '').encode()).hexdigest() posts.setdefault(key, {'post': p, 'seo': s, 'urls': []})['urls'].append(r['url']) with (DOCS / 'legado-conteudos-reconciliados.csv').open('w', encoding='utf-8-sig', newline='') as f: w = csv.writer(f) w.writerow(['ID legado', 'Título', 'Autor', 'Categoria', 'Publicado', 'Modificado', 'SHA256 conteúdo', 'URLs observadas', 'Recurso QA', 'Situação']) for key, item in posts.items(): p, s = item['post'], item['seo'] w.writerow([p['id'], p['title'], p.get('author'), p.get('category'), s.get('publishedTime'), s.get('modifiedTime'), key.rsplit('|', 1)[1], ' | '.join(item['urls']), '6' if p['id'] == 171985 else '', 'recurso único QA; não é oportunidade' if p['id'] == 171985 else 'inventariado; cadastro/versão por reconciliar']) sitemap = next(r for r in rows if r['url'].endswith('/sitemap.xml')) tree = ET.fromstring((ROOT / sitemap['private_file']).read_bytes()) ns = {'s': 'http://www.sitemaps.org/schemas/sitemap/0.9'} with (DOCS / 'legado-sitemap-anunciado.csv').open('w', encoding='utf-8-sig', newline='') as f: w = csv.writer(f) w.writerow(['URL anunciada', 'Última alteração anunciada', 'Situação']) for item in tree.findall('s:url', ns): w.writerow([item.findtext('s:loc', namespaces=ns), item.findtext('s:lastmod', namespaces=ns), 'anunciado no sitemap; domínio infopublica; conteúdo não recolhido']) print(json.dumps({'graph_records': len(rows), 'reconciled_observed_post_versions': len(posts), 'sitemap_announced': len(tree.findall('s:url', ns)), 'pilot_documents': 2, 'article_content_sha256': hashlib.sha256(post['content'].encode()).hexdigest(), 'original_image_sha256': image['sha256']}))