iscc-app / scripts /build_cert_format_catalog.py
Couscouskabyle's picture
Deploy Sinetti V2 (FastAPI + React) [publication:363e1b82420d4c339dabaf2b4417eadc]
0fd673c verified
Raw History Blame
7.22 kB
"""Build the certificate-format catalog from the parsed PDF artifact."""
from __future__ import annotations
import argparse
import gzip
import json
import shutil
import sqlite3
import sys
import tempfile
from datetime import datetime, timezone
from pathlib import Path
from urllib.request import Request, urlopen
ROOT = Path(__file__).resolve().parents[1]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from collections import defaultdict
from sinetti_services.cert_format_catalog import (
FormatDoc,
artifact_sha256,
build_catalog,
choose_bucket,
load_format_docs,
pick_exemplar,
thumb_filename,
type_key,
write_catalog,
)
from cert_pdf_models import PdfDocumentRef
from cert_pdf_ocr import _render_pdf_pages
from ins import _doc_operator_id
def _utc_now() -> str:
return datetime.now(timezone.utc).isoformat(timespec="seconds")
def _iter_ins_records(value):
if isinstance(value, list):
for item in value:
yield from _iter_ins_records(item)
elif isinstance(value, dict):
if (
"certificate_id" in value
or "codiceDelCertificato" in value
):
yield value
else:
for item in value.values():
yield from _iter_ins_records(item)
def _load_ins_cb_map(path: Path) -> dict[str, str]:
"""Map INS operator app ids to the newest-issued non-empty issuing CB.
Equal or missing issue dates keep the first non-empty CB encountered.
"""
if not path.exists():
return {}
payload = json.loads(path.read_text(encoding="utf-8"))
best: dict[str, tuple[str, str]] = {}
for record in _iter_ins_records(payload):
operator_id = _doc_operator_id(record)
issuing_cb = str(
record.get("issuing_cb")
or record.get("organismoDiCertificazione")
or ""
).strip()
issued = str(record.get("issued") or "").strip()
previous = best.get(operator_id)
if operator_id and issuing_cb and (
previous is None or issued > previous[0]
):
best[operator_id] = (issued, issuing_cb)
return {
operator_id: issuing_cb
for operator_id, (_issued, issuing_cb) in best.items()
}
def _open_artifact(path: Path):
if path.suffix != ".gz":
return sqlite3.connect(path), None
temp_dir = tempfile.TemporaryDirectory(prefix="sinetti_cert_formats_")
sqlite_path = Path(temp_dir.name) / "details.sqlite"
with gzip.open(path, "rb") as source, sqlite_path.open("wb") as target:
shutil.copyfileobj(source, target)
return sqlite3.connect(sqlite_path), temp_dir
def _fetch_pdf_bytes(doc: FormatDoc) -> bytes:
if doc.scheme == "REDcert" or doc.pdf_url.startswith("redcert://"):
try:
from redcert_pdf_store import load_hosted_pdf
hosted = load_hosted_pdf(doc.certificate_id)
if hosted:
return bytes(hosted[0])
except Exception:
pass
from cert_pdf_parsers.redcert import fetch_redcert_pdf_bytes
return fetch_redcert_pdf_bytes(
PdfDocumentRef(
scheme=doc.scheme,
certificate_id=doc.certificate_id,
pdf_url=doc.pdf_url or f"redcert://{doc.certificate_id}",
)
)
if not doc.pdf_url.startswith(("http://", "https://")):
return b""
request = Request(
doc.pdf_url,
headers={"User-Agent": "Sinetti cert-format catalog builder"},
)
with urlopen(request, timeout=30) as response:
return response.read()
def _exemplar_docs_by_type_key(docs: list[FormatDoc]) -> dict[str, FormatDoc]:
grouped: dict[tuple[str, str, str], list[FormatDoc]] = defaultdict(list)
for doc in docs:
bucket_kind, bucket_id = choose_bucket(
doc.scheme,
doc.parse_status,
list(doc.reason_codes),
ins_cb=doc.ins_cb,
)
grouped[(doc.scheme, bucket_kind, bucket_id)].append(doc)
return {
type_key(scheme, bucket_kind, bucket_id): pick_exemplar(bucket_docs)
for (scheme, bucket_kind, bucket_id), bucket_docs in grouped.items()
}
def _add_thumbnails(
catalog: dict,
docs: list[FormatDoc],
*,
thumbs_dir: Path,
) -> None:
exemplars = _exemplar_docs_by_type_key(docs)
for entries in catalog["schemes"].values():
for entry in entries:
try:
doc = exemplars[entry["type_key"]]
pdf_bytes = _fetch_pdf_bytes(doc)
if not pdf_bytes:
continue
rendered, _diagnostics = _render_pdf_pages(
pdf_bytes,
pages=[1],
dpi=110,
)
if not rendered:
continue
filename = thumb_filename(entry["type_key"])
thumbs_dir.mkdir(parents=True, exist_ok=True)
rendered[0][1].save(thumbs_dir / filename, "PNG")
entry["thumbnail"] = f"cert_format_thumbs/{filename}"
except Exception:
continue
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument(
"--artifact",
default=str(ROOT / "data" / "cert_pdf_details.sqlite.gz"),
)
parser.add_argument(
"--out",
default=str(ROOT / "data" / "cert_format_catalog.json"),
)
parser.add_argument(
"--ins-index",
default=str(ROOT / "data" / "ins" / "cert_pdfs.json"),
)
parser.add_argument("--thumbs-dir", default="")
parser.add_argument("--skip-thumbs", action="store_true")
args = parser.parse_args(argv)
artifact = Path(args.artifact)
out = Path(args.out)
conn, temp_dir = _open_artifact(artifact)
try:
docs = load_format_docs(
conn,
ins_cb_by_cert_id=_load_ins_cb_map(Path(args.ins_index)),
)
finally:
conn.close()
if temp_dir is not None:
temp_dir.cleanup()
catalog = build_catalog(docs, built_at=_utc_now())
# Lets the freshness guard (tests/test_cert_format_catalog_fresh.py) prove this catalog
# describes the committed details artifact, not an older parse.
catalog["source_sha256"] = artifact_sha256(artifact)
if not args.skip_thumbs:
thumbs_dir = (
Path(args.thumbs_dir)
if args.thumbs_dir
else out.parent / "cert_format_thumbs"
)
_add_thumbnails(catalog, docs, thumbs_dir=thumbs_dir)
write_catalog(out, catalog)
report = {
"status": "ok",
"types": sum(len(entries) for entries in catalog["schemes"].values()),
"docs": len(docs),
"path": str(out),
}
print(json.dumps(report, indent=2, sort_keys=True))
return 0
if __name__ == "__main__":
raise SystemExit(main())