← Files VeraARCHIVED FILE

modules/management-control-pack/vendor/modules/reporting_table.py

16.7 KB · Oct 2, 2026 · 00:29 UTC

↓ Download file

"""Shared IBCS-style reporting table, extracted from Period Comparison.

Render supplied comparison rows only. Source selection and favorable/adverse
meaning remain with the caller; bar geometry and number formatting are mechanical.
"""

from __future__ import annotations

import math
from html import escape
from pathlib import Path
from typing import Any

__all__ = ["render_reporting_table", "write_reporting_table"]
TOLERANCE = 0.000001
DISPLAY_LABELS = {
    "en": ("Scenario", "Change", "% change", "n/a"),
    "it": ("Confronto", "Scostamento", "Δ %", "n/d"),
    "fr": ("Scénario", "Écart", "Écart %", "n.d."),
    "de": ("Szenario", "Abweichung", "Abweichung %", "k. A."),
    "es": ("Escenario", "Variación", "% variación", "n/d"),
}


def _table_value_scale(rows: list[dict[str, Any]]) -> tuple[float, str]:
    """Return a compact display scale and label for table values."""

    values = [
        abs(float(row.get(key) or 0.0))
        for row in rows
        for key in ("baseline_value", "comparison_value", "absolute_variance")
    ]
    max_value = max(values or [0.0])
    if max_value >= 1_000_000:
        return 1_000_000.0, "m"
    if max_value >= 1_000:
        return 1_000.0, "k"
    return 1.0, "units"


def _format_scaled_value(
    value: float, scale: float, *, signed: bool = False, language: str = "en"
) -> str:
    """Format table values with tabular, compact notation."""

    scaled = value / scale
    if scaled == 0:
        scaled = 0.0
    prefix = "+" if signed and scaled > 0 else ""
    text = f"{prefix}{scaled:,.0f}" if scale == 1.0 else f"{prefix}{scaled:,.1f}"
    if language == "fr":
        return text.translate(str.maketrans({",": "\u202f", ".": ","}))
    if language in {"it", "de", "es"}:
        return text.translate(str.maketrans({",": ".", ".": ","}))
    return text


def _format_relative_percent(value: float | None, language: str = "en") -> str:
    """Format a signed relative variance."""

    if value is None or math.isnan(value):
        return DISPLAY_LABELS.get(language, DISPLAY_LABELS["en"])[3]
    text = f"{value:+.1f}%" if value else "0.0%"
    return text.replace(".", ",") if language in {"it", "fr", "de", "es"} else text


def _variance_marker_html(
    *,
    value: float | None,
    label: str,
    max_abs_value: float,
    marker_style: str = "bar",
) -> str:
    """Return centered positive/negative variance marker markup."""

    if value is None or math.isnan(float(value)):
        return (
            '<span class="variance-marker">'
            '<span class="variance-lane negative-lane"></span>'
            '<span class="variance-axis"></span>'
            '<span class="variance-lane positive-lane"></span>'
            f'<span class="variance-label muted">{escape(label)}</span>'
            "</span>"
        )
    value = float(value)
    width = 0.0
    if max_abs_value > TOLERANCE:
        width = min(100.0, abs(value) / max_abs_value * 100)
    sign_class = (
        "positive"
        if value > TOLERANCE
        else "negative" if value < -TOLERANCE else "neutral"
    )
    negative_width = width if sign_class == "negative" else 0.0
    positive_width = width if sign_class == "positive" else 0.0
    if marker_style == "pin":
        negative_pin = (
            f'<span class="variance-pin-line negative" style="width:{negative_width:.1f}%">'
            '<span class="variance-pin"></span></span>'
            if sign_class == "negative"
            else ""
        )
        positive_pin = (
            f'<span class="variance-pin-line positive" style="width:{positive_width:.1f}%">'
            '<span class="variance-pin"></span></span>'
            if sign_class == "positive"
            else ""
        )
        neutral_pin = (
            '<span class="variance-zero-pin"><span class="variance-pin"></span></span>'
            if sign_class == "neutral"
            else ""
        )
        return (
            '<span class="variance-marker">'
            f'<span class="variance-lane negative-lane">{negative_pin}</span>'
            f'<span class="variance-axis">{neutral_pin}</span>'
            f'<span class="variance-lane positive-lane">{positive_pin}</span>'
            f'<span class="variance-label {sign_class}">{escape(label)}</span>'
            "</span>"
        )
    return (
        '<span class="variance-marker">'
        '<span class="variance-lane negative-lane">'
        f'<span class="variance-bar negative" style="width:{negative_width:.1f}%"></span>'
        "</span>"
        '<span class="variance-axis"></span>'
        '<span class="variance-lane positive-lane">'
        f'<span class="variance-bar positive" style="width:{positive_width:.1f}%"></span>'
        "</span>"
        f'<span class="variance-label {sign_class}">{escape(label)}</span>'
        "</span>"
    )


def render_reporting_table(
    *,
    row_header: str,
    rows: list[dict[str, Any]],
    baseline_label: str,
    comparison_label: str,
    entity_label: str,
    comparison_caption: str,
    metric: str,
    source_label: str,
    language: str = "en",
    fragment: bool = False,
    row_label_width: int | None = None,
    scale_rows: list[dict[str, Any]] | None = None,
    value_scale: int | None = None,
    value_width: int = 88,
) -> str:
    """Write a compact scenario and variance table artifact."""

    # An interactive group shares its unit and variance scales across views.
    scale_basis = rows if scale_rows is None else [*rows, *scale_rows]
    scale, scale_label = _table_value_scale(scale_basis)
    if value_scale is not None:
        if value_scale not in {1, 1000, 1000000}:
            raise ValueError("value_scale must be 1, 1000 or 1000000")
        scale, scale_label = (
            float(value_scale),
            {1: "units", 1000: "k", 1000000: "m"}[value_scale],
        )
    measure_suffix = (
        ""
        if scale_label == "units"
        else (
            (" × 1.000" if scale_label == "k" else " × 1.000.000")
            if language == "it"
            else (
                f" en {scale_label}"
                if language in {"es", "fr"}
                else f" in {scale_label}"
            )
        )
    )
    max_abs_absolute = max(
        (abs(float(row["absolute_variance"])) for row in scale_basis),
        default=0.0,
    )
    max_abs_relative = max(
        (
            abs(float(row["relative_variance"]))
            for row in scale_basis
            if row.get("relative_variance") is not None
        ),
        default=0.0,
    )
    row_label_width = row_label_width or (
        92 if row_header in {"Period", "Periodo"} else 124
    )
    variance_width = 172
    table_width = row_label_width + (value_width * 2) + (variance_width * 2)
    page_width = table_width + 56
    table_rows: list[str] = []
    for index, row in enumerate(rows):
        absolute_variance = float(row["absolute_variance"])
        sign_class = (
            "positive"
            if absolute_variance > TOLERANCE
            else "negative" if absolute_variance < -TOLERANCE else "neutral"
        )
        relative_variance = row.get("relative_variance")
        relative_value = None if relative_variance is None else float(relative_variance)
        table_rows.append(
            '<tr class="'
            + (
                "summary-row"
                if row.get("row_type") in {"subtotal", "total"}
                or (index == 0 and row_header == "Window")
                else "detail-row"
            )
            + (
                " lower"
                if row.get("favorable_direction") == "lower"
                else (
                    " neutral-direction"
                    if row.get("favorable_direction") == "neutral"
                    else ""
                )
            )
            + f'" data-absolute-variance="{escape(str(row["absolute_variance"]), quote=True)}">'
            f"<th>{escape(str(row['row_label']))}</th>"
            f"<td>{escape(_format_scaled_value(float(row['baseline_value']), scale, language=language))}</td>"
            f"<td>{escape(_format_scaled_value(float(row['comparison_value']), scale, language=language))}</td>"
            f'<td class="variance-cell group-start {sign_class}">'
            f"{_variance_marker_html(value=absolute_variance, label=_format_scaled_value(absolute_variance, scale, signed=True, language=language), max_abs_value=max_abs_absolute)}</td>"
            f'<td class="variance-cell">'
            f"{_variance_marker_html(value=relative_value, label=_format_relative_percent(relative_value, language), max_abs_value=max_abs_relative, marker_style='pin')}</td>"
            "</tr>"
        )
    html = f"""<!doctype html>
<html lang="{escape(language)}">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>{escape(entity_label)} - {escape(metric)}</title>
  <style>
    .reporting-comparison {{
      --ink: #0f1114;
      --muted: #5d6670;
      --rule: #c9cdd1;
      --heavy: #111;
      --pin: #222;
      --soft: #f3f4f5;
      --negative: #e22a1d;
      --positive: #86ad00;
      --neutral: #444;
    }}
    .reporting-comparison * {{
      box-sizing: border-box;
    }}
    .reporting-comparison {{
      margin: 0;
      background: #fff;
      color: var(--ink);
      font: 14px/1.2 Arial, Helvetica, sans-serif;
      font-variant-numeric: tabular-nums;
    }}
    .reporting-comparison .page {{
      width: {page_width}px;
      padding: 24px 28px 20px;
      background: #fff;
    }}
    .reporting-comparison .title-block {{
      border-bottom: 2px solid #858585;
      padding-bottom: 24px;
      width: {table_width}px;
    }}
    .reporting-comparison .title-line {{
      margin: 0;
      color: var(--ink);
      font-size: 14px;
      line-height: 1.18;
    }}
    .reporting-comparison .title-metric strong {{
      font-weight: 700;
    }}
    .reporting-comparison table {{
      border-collapse: collapse;
      margin-top: 14px;
      table-layout: fixed;
      width: {table_width}px;
    }}
    col.row-label {{ width: {row_label_width}px; }}
    col.value {{ width: {value_width}px; }}
    col.variance {{ width: {variance_width}px; }}
    .reporting-comparison thead tr.group th {{
      border-bottom: 0;
      color: var(--ink);
      font-size: 14px;
      font-weight: 700;
      padding: 0 8px 4px;
      text-align: center;
    }}
    .reporting-comparison thead tr.labels th {{
      border-bottom: 2px solid var(--heavy);
      font-size: 14px;
      font-weight: 700;
      padding: 5px 8px 7px;
      text-align: right;
    }}
    .reporting-comparison thead tr.labels th:first-child,
    .reporting-comparison tbody th {{
      text-align: left;
    }}
    .reporting-comparison tbody th,
    .reporting-comparison tbody td {{
      border-bottom: 1px solid var(--rule);
      font-size: 14px;
      height: 31px;
      padding: 5px 7px;
      text-align: right;
      vertical-align: middle;
      white-space: nowrap;
      min-width: 0;
    }}
    .reporting-comparison tbody th {{
      font-weight: 500;
      text-align: left;
    }}
    .reporting-comparison tbody tr.summary-row th,
    .reporting-comparison tbody tr.summary-row td {{
      border-bottom: 2px solid var(--heavy);
      font-weight: 700;
    }}
    .reporting-comparison .group-start {{
      border-left: 2px solid var(--heavy);
    }}
    .reporting-comparison .negative {{
      color: var(--negative);
    }}
    .reporting-comparison .positive {{
      color: var(--positive);
    }}
    .reporting-comparison .neutral {{
      color: var(--neutral);
    }}
    .reporting-comparison .muted {{
      color: var(--muted);
    }}
    .reporting-comparison .variance-cell {{
      padding-left: 8px;
    }}
    .reporting-comparison .variance-marker {{
      display: grid;
      gap: 0;
      grid-template-columns: 50px 2px 50px 56px;
    }}
    .reporting-comparison .variance-lane {{
      align-items: center;
      display: flex;
      height: 18px;
    }}
    .reporting-comparison .negative-lane {{
      justify-content: flex-end;
    }}
    .reporting-comparison .positive-lane {{
      justify-content: flex-start;
    }}
    .reporting-comparison .variance-axis {{
      background: var(--heavy);
      display: block;
      height: 22px;
      margin-top: -2px;
      width: 2px;
    }}
    .reporting-comparison .variance-bar {{
      display: block;
      height: 11px;
      min-width: 0;
    }}
    .reporting-comparison .variance-bar.negative {{
      background: var(--negative);
    }}
    .reporting-comparison .variance-bar.positive {{
      background: var(--positive);
    }}
    .reporting-comparison .variance-pin-line {{
      align-items: center;
      display: flex;
      height: 2px;
      min-width: 0;
    }}
    .reporting-comparison .variance-pin-line.negative {{
      background: var(--negative);
      justify-content: flex-start;
    }}
    .reporting-comparison .variance-pin-line.positive {{
      background: var(--positive);
      justify-content: flex-end;
    }}
    .reporting-comparison .variance-pin {{
      background: var(--pin);
      display: block;
      flex: 0 0 auto;
      height: 7px;
      width: 7px;
    }}
    .reporting-comparison .variance-zero-pin {{
      align-items: center;
      display: flex;
      height: 22px;
      justify-content: center;
      left: -3px;
      position: relative;
      width: 7px;
    }}
    .reporting-comparison .variance-label {{
      font-size: 14px;
      line-height: 18px;
      padding-left: 8px;
      text-align: right;
    }}
    .reporting-comparison .source {{
      border-top: 1px solid var(--rule);
      color: var(--muted);
      font-size: 14px;
      margin-top: 14px;
      padding-top: 8px;
    }}
    .reporting-comparison {{ max-width: 100%; overflow-x: auto; }}
    .reporting-comparison .page {{ padding: 20px 0; margin: 0; }}
    .reporting-comparison .title-block {{ border-top: 0; padding-top: 0; }}
    .reporting-comparison th {{ background: #fff; color: var(--ink); }}
    .reporting-comparison tbody th {{ white-space: normal; overflow-wrap: break-word; }}
    .reporting-comparison .lower .variance-bar.positive,
    .reporting-comparison .lower .variance-pin-line.positive {{ background: var(--negative); }}
    .reporting-comparison .lower .variance-bar.negative,
    .reporting-comparison .lower .variance-pin-line.negative {{ background: var(--positive); }}
    .reporting-comparison .lower .positive {{ color: var(--negative); }}
    .reporting-comparison .lower .negative {{ color: var(--positive); }}
    .reporting-comparison .neutral-direction .positive,
    .reporting-comparison .neutral-direction .negative {{ color: var(--neutral); }}
    .reporting-comparison .neutral-direction .variance-bar,
    .reporting-comparison .neutral-direction .variance-pin-line {{ background: var(--neutral); }}
    @media print {{
      .reporting-comparison {{ overflow: visible; break-inside: avoid-page; }}
      .reporting-comparison .page,
      .reporting-comparison .title-block,
      .reporting-comparison table {{ width: 100%; }}
      .reporting-comparison col.row-label {{ width: 24%; }}
      .reporting-comparison col.value {{ width: 12%; }}
      .reporting-comparison col.variance {{ width: 26%; }}
    }}
  </style>
</head>
<body>
<div class="reporting-comparison">
  <main class="page" data-gallery-screenshot>
    <header class="title-block">
      <p class="title-line">{escape(entity_label)}</p>
      <p class="title-line title-metric"><strong>{escape(metric)}</strong>{escape(measure_suffix)}</p>
      <p class="title-line">{escape(comparison_caption)}</p>
    </header>
    <table>
      <colgroup>
        <col class="row-label">
        <col class="value">
        <col class="value">
        <col class="variance">
        <col class="variance">
      </colgroup>
      <thead>
        <tr class="group">
          <th></th>
          <th colspan="2">{DISPLAY_LABELS.get(language, DISPLAY_LABELS['en'])[0]}</th>
          <th class="group-start" colspan="2">{DISPLAY_LABELS.get(language, DISPLAY_LABELS['en'])[1]}</th>
        </tr>
        <tr class="labels">
          <th>{escape(row_header)}</th>
          <th>{escape(baseline_label)}</th>
          <th>{escape(comparison_label)}</th>
          <th class="group-start">Δ</th>
          <th>{DISPLAY_LABELS.get(language, DISPLAY_LABELS['en'])[2]}</th>
        </tr>
      </thead>
      <tbody>
        {''.join(table_rows)}
      </tbody>
    </table>
    <div class="source">{escape(source_label)}</div>
  </main>
</div>
</body>
</html>
"""
    if fragment:
        style = html.split("<style>", 1)[1].split("</style>", 1)[0]
        body = html.split("<body>", 1)[1].split("</body>", 1)[0]
        return "<style>" + style + "</style>" + body
    return html


def write_reporting_table(*, output_path: Path, **kwargs: Any) -> None:
    """Write the same reporting component as a standalone HTML artifact."""
    output_path.write_text(render_reporting_table(**kwargs), encoding="utf-8")

SHA-256: 99b687b83f4ce62fb2a33c875cf5740edb0854e9a7b094f1c314b1b6198adc1e