"""render_automatic_eda_pptx — chapter-based EDA report as a 16:9 PPTX deck. Public ``eda``-group entry point that renders an AutomaticEDA document (a list of chapters, or an ``eda`` TableProfile from which the canonical chapters are built) into a PowerPoint deck for sharing. Same anti-cut principle as the PDF renderer: every block is measured and, when it does not fit, continues on a new slide titled `` (cont.)``; data tables split by rows repeating the header; matplotlib figures are exported to PNG and inserted scaled to fit entirely. Each slide is stamped `` · v`` and a per-chapter manifest (``automatic_eda_manifest.json``) is written next to the output. dict-no-throw: never raises. Returns ``{path, n_slides, chapters, manifest_path, note}``; on a fatal error ``path`` is None and ``note`` explains why (e.g. python-pptx not installed). Engine: ``python-pptx`` (added dependency; declared in python/pyproject.toml). """ from __future__ import annotations import os from datascience.automatic_eda import build_document, merge_manifest, render_pptx from datascience.automatic_eda.model import as_chapter, as_chapters def _coerce_chapters(chapters_or_profile, meta: dict) -> list: """Accept chapters OR an eda profile and return a list of Chapter.""" arg = chapters_or_profile if isinstance(arg, (list, tuple)): return as_chapters(list(arg)) if isinstance(arg, dict): if "blocks" in arg and "columns" not in arg: ch = as_chapter(arg) return [ch] if ch is not None else [] return build_document(arg, (meta or {}).get("ctx")) return [] def render_automatic_eda_pptx(chapters_or_profile, out_path: str, meta: dict = None) -> dict: """Render an AutomaticEDA document into a shareable PPTX deck. Args: chapters_or_profile: a list of chapters (``Chapter`` dataclasses or dicts) or an ``eda`` TableProfile dict (chapters built via ``build_document(profile, meta['ctx'])``). out_path: filesystem path for the PPTX (parent dirs are created). meta: optional dict. Recognised keys: ``title``, ``ctx``, ``manifest_path`` (defaults to ``automatic_eda_manifest.json`` beside ``out_path``), ``write_manifest`` (False to skip), ``generated_at``. Returns: dict (never raises): ``{path, n_slides, chapters, manifest_path, note}``. """ meta = dict(meta or {}) chapters = _coerce_chapters(chapters_or_profile, meta) result = render_pptx(chapters, out_path, meta) manifest_path = None if meta.get("write_manifest", True) and result.get("path"): manifest_path = meta.get("manifest_path") if not manifest_path: manifest_path = os.path.join( os.path.dirname(os.path.abspath(out_path)), "automatic_eda_manifest.json") generated_at = meta.get("generated_at") or _now_iso() merge_manifest(manifest_path, "pptx", result.get("chapters") or [], generated_at) result["manifest_path"] = manifest_path return result def _now_iso() -> str: from datetime import datetime, timezone return datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")