Files
Investidor-V2/tools/legacy-content-manifest.py
nala 20971d523f
tests / ci (push) Canceled after 0s
oportunidades 6
2026-10-10 21:17:46 +02:00

72 lines
5.9 KiB
Python

"""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']}))