Coverage for src/lexigram/admin/ui/organisms/table/views/summarizers.py: 100%
45 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-21 14:22 +0800
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-21 14:22 +0800
1"""Per-column table summarizers for footer aggregates.
3Computes aggregate footer values (sum, average, count, range) for
4columns declared with ``Column.summarizer()`` against the currently
5visible page of records.
6"""
8from __future__ import annotations
10from typing import Any
12SUMMARIZER_LABELS: dict[str, str] = {
13 "sum": "Sum",
14 "average": "Average",
15 "count": "Count",
16 "range": "Range",
17}
19SUMMARIZER_OPERATORS = frozenset(SUMMARIZER_LABELS)
22def _column_values(rows: list[Any], column: Any) -> list[Any]:
23 """Collect non-empty raw values for a column across records."""
24 values: list[Any] = []
25 for row in rows:
26 value = column.get_value(row)
27 if value is None or value == "":
28 continue
29 values.append(value)
30 return values
33def _numeric(values: list[Any]) -> list[float]:
34 """Coerce values to floats, skipping non-numeric entries."""
35 numeric: list[float] = []
36 for value in values:
37 try:
38 numeric.append(float(value))
39 except (TypeError, ValueError):
40 continue
41 return numeric
44def _format(number: float) -> str:
45 """Format a number without trailing zeros for integral values."""
46 if number == int(number):
47 return str(int(number))
48 return f"{number:.2f}".rstrip("0").rstrip(".")
51def compute_summaries(rows: list[Any], columns: list[Any]) -> dict[str, str]:
52 """Compute footer summaries for columns with a configured summarizer.
54 Args:
55 rows: Records on the current page.
56 columns: Table columns; only those with ``_summarizer`` set are
57 considered.
59 Returns:
60 Mapping of column name to display string, e.g. ``"Sum 42"``.
61 Columns whose data yields no usable values are omitted.
62 """
63 summaries: dict[str, str] = {}
64 for column in columns:
65 operator = getattr(column, "_summarizer", None)
66 if operator not in SUMMARIZER_OPERATORS:
67 continue
68 values = _column_values(rows, column)
69 label = SUMMARIZER_LABELS[operator]
70 if operator == "count":
71 summaries[column.name] = f"{label} {len(values)}"
72 continue
73 numeric = _numeric(values)
74 if not numeric:
75 continue
76 if operator == "sum":
77 summaries[column.name] = f"{label} {_format(sum(numeric))}"
78 elif operator == "average":
79 summaries[column.name] = f"{label} {_format(sum(numeric) / len(numeric))}"
80 elif operator == "range":
81 summaries[column.name] = (
82 f"{label} {_format(min(numeric))} - {_format(max(numeric))}"
83 )
84 return summaries