72 lines
5.9 KiB
Python
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']}))
|