Source code for qca.reporting.jupyter

"""Jupyter-friendly summary objects."""

from __future__ import annotations

from dataclasses import dataclass
from html import escape
from typing import Any

import pandas as pd

from qca.reporting.reproducibility import collect_reproducibility_metadata


[docs] @dataclass(frozen=True) class JupyterSummary: """Small display object with plain text and HTML representations.""" title: str text: str html: str def _repr_html_(self) -> str: return self.html def _repr_markdown_(self) -> str: return self.text def __str__(self) -> str: return self.text
[docs] def jupyter_summary( obj: Any, *, title: str | None = None, max_rows: int = 8, ) -> JupyterSummary: """Build a compact summary suitable for notebooks.""" summary_title = title or _default_title(obj) metadata = collect_reproducibility_metadata(obj) metrics = _summary_metrics(obj) text_lines = [f"# {summary_title}", ""] for key, value in metrics.items(): text_lines.append(f"- {key}: {value}") text_lines.append(f"- qca_version: {metadata.get('qca_version')}") html_parts = [ '<div class="pyqca-summary" style="font-family: system-ui, sans-serif;">', f"<h3>{escape(summary_title)}</h3>", '<table style="border-collapse: collapse;">', ] for key, value in metrics.items(): html_parts.append( "<tr>" f'<th style="text-align:left;padding:2px 8px;">{escape(str(key))}</th>' f'<td style="padding:2px 8px;">{escape(str(value))}</td>' "</tr>" ) html_parts.append( "<tr>" '<th style="text-align:left;padding:2px 8px;">qca_version</th>' f'<td style="padding:2px 8px;">{escape(str(metadata.get("qca_version")))}</td>' "</tr>" ) html_parts.append("</table>") table = _primary_table(obj) if table is not None and not table.empty: html_parts.append("<h4>Preview</h4>") html_parts.append(table.head(max_rows).to_html(index=False, escape=True)) html_parts.append("</div>") return JupyterSummary( title=summary_title, text="\n".join(text_lines), html="\n".join(html_parts), )
def _summary_metrics(obj: Any) -> dict[str, Any]: metrics: dict[str, Any] = { "object_type": type(obj).__name__, } for attr in ( "qca_type", "outcome", "workflow", "selected_solution_type", "consistency", "coverage", ): if hasattr(obj, attr): value = getattr(obj, attr) if isinstance(value, float): value = f"{value:.4f}" metrics[attr] = value if hasattr(obj, "solutions"): solutions = obj.solutions if isinstance(solutions, pd.DataFrame): metrics["n_solution_rows"] = len(solutions) if hasattr(obj, "truth_table"): truth_table = obj.truth_table if isinstance(truth_table, pd.DataFrame): metrics["n_truth_table_rows"] = len(truth_table) if hasattr(obj, "summary_df"): summary_df = obj.summary_df if isinstance(summary_df, pd.DataFrame): metrics["n_summary_rows"] = len(summary_df) for attr in ("n_models", "n_valid_models", "best_model_id"): if hasattr(obj, attr): metrics[attr] = getattr(obj, attr) return metrics def _primary_table(obj: Any) -> pd.DataFrame | None: for attr in ( "candidate_models", "pareto_models", "solutions", "summary_df", "truth_table", "condition_schema", ): if hasattr(obj, attr): value = getattr(obj, attr) if isinstance(value, pd.DataFrame): return value return None def _default_title(obj: Any) -> str: if hasattr(obj, "qca_type"): return f"{obj.qca_type} Summary" return f"{type(obj).__name__} Summary" __all__ = [ "JupyterSummary", "jupyter_summary", ]