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

1"""Per-column table summarizers for footer aggregates. 

2 

3Computes aggregate footer values (sum, average, count, range) for 

4columns declared with ``Column.summarizer()`` against the currently 

5visible page of records. 

6""" 

7 

8from __future__ import annotations 

9 

10from typing import Any 

11 

12SUMMARIZER_LABELS: dict[str, str] = { 

13 "sum": "Sum", 

14 "average": "Average", 

15 "count": "Count", 

16 "range": "Range", 

17} 

18 

19SUMMARIZER_OPERATORS = frozenset(SUMMARIZER_LABELS) 

20 

21 

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 

31 

32 

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 

42 

43 

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(".") 

49 

50 

51def compute_summaries(rows: list[Any], columns: list[Any]) -> dict[str, str]: 

52 """Compute footer summaries for columns with a configured summarizer. 

53 

54 Args: 

55 rows: Records on the current page. 

56 columns: Table columns; only those with ``_summarizer`` set are 

57 considered. 

58 

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