Coverage for src/jquantstats/_reports/_formatting.py: 100%
20 statements
« prev ^ index » next coverage.py v7.17.1, created at 2026-11-03 15:52 +0000
« prev ^ index » next coverage.py v7.17.1, created at 2026-11-03 15:52 +0000
1"""Shared formatting helpers for report modules."""
3from __future__ import annotations
5import math
6from pathlib import Path
7from typing import Any, TypeGuard
9import plotly.io as pio
10from jinja2 import Environment, FileSystemLoader, select_autoescape
12# templates/ lives one level above this subpackage (at src/jquantstats/templates/)
13_TEMPLATES_DIR = Path(__file__).parent.parent / "templates"
14_env = Environment(
15 loader=FileSystemLoader(_TEMPLATES_DIR),
16 autoescape=select_autoescape(["html"]),
17)
20def _is_finite(v: Any) -> TypeGuard[int | float]:
21 """Return True when *v* is a real, finite number."""
22 if not isinstance(v, (int, float)):
23 return False
24 return math.isfinite(float(v))
27def _fmt(value: Any, fmt: str = ".4f", suffix: str = "") -> str:
28 """Format *value* for display; return ``"N/A"`` for non-finite values."""
29 if not _is_finite(value):
30 return "N/A"
31 return f"{float(value):{fmt}}{suffix}"
34def _plotly_div(fig: Any, include_plotlyjs: bool | str = False) -> str:
35 """Serialise a Plotly figure to a standalone HTML ``<div>``."""
36 return str(pio.to_html(fig, full_html=False, include_plotlyjs=include_plotlyjs))
39def _table_html(header_cells: str, body_html: str) -> str:
40 """Wrap pre-rendered table cells and body rows in the shared report table shell."""
41 return (
42 '<table class="stats-table">'
43 "<thead><tr>"
44 f'<th class="metric-header">Metric</th>{header_cells}'
45 "</tr></thead>"
46 f"<tbody>{body_html}</tbody>"
47 "</table>"
48 )