Skip to content

Commit cddde63

Browse files
authored
Create reporting.py
1 parent 7232c95 commit cddde63

1 file changed

Lines changed: 153 additions & 0 deletions

File tree

src/ohip_bench/reporting.py

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
"""
2+
Benchmark reporting helpers for IX-HapticSight.
3+
4+
This module provides small, backend-agnostic utilities for:
5+
- summarizing benchmark result sets
6+
- grouping results by domain or outcome
7+
- exporting report-friendly dictionaries
8+
9+
The goal is to make benchmark outputs easier to inspect and compare without
10+
pulling presentation logic into the benchmark runner itself.
11+
"""
12+
13+
from __future__ import annotations
14+
15+
from dataclasses import dataclass
16+
from typing import Iterable
17+
18+
from .models import BenchmarkDomain, BenchmarkOutcome, BenchmarkResult
19+
20+
21+
@dataclass(frozen=True)
22+
class BenchmarkSummary:
23+
"""
24+
Compact aggregate summary for a benchmark result set.
25+
"""
26+
27+
total: int
28+
passed: int
29+
failed: int
30+
errored: int
31+
skipped: int
32+
average_duration_ms: float
33+
by_domain: dict[str, int]
34+
by_outcome: dict[str, int]
35+
36+
def to_dict(self) -> dict[str, object]:
37+
return {
38+
"total": int(self.total),
39+
"passed": int(self.passed),
40+
"failed": int(self.failed),
41+
"errored": int(self.errored),
42+
"skipped": int(self.skipped),
43+
"average_duration_ms": float(self.average_duration_ms),
44+
"by_domain": dict(self.by_domain),
45+
"by_outcome": dict(self.by_outcome),
46+
}
47+
48+
49+
def summarize_results(results: Iterable[BenchmarkResult]) -> BenchmarkSummary:
50+
"""
51+
Summarize a benchmark result set.
52+
53+
The summary is intentionally small and deterministic so it can be reused in:
54+
- local CLI-style reporting later
55+
- JSON exports
56+
- README/docs examples
57+
- CI artifact checks
58+
"""
59+
result_list = list(results)
60+
total = len(result_list)
61+
62+
by_domain: dict[str, int] = {}
63+
by_outcome: dict[str, int] = {}
64+
65+
passed = failed = errored = skipped = 0
66+
duration_sum = 0.0
67+
68+
for result in result_list:
69+
domain_key = result.domain.value
70+
outcome_key = result.outcome.value
71+
72+
by_domain[domain_key] = by_domain.get(domain_key, 0) + 1
73+
by_outcome[outcome_key] = by_outcome.get(outcome_key, 0) + 1
74+
75+
duration_sum += float(result.duration_ms)
76+
77+
if result.outcome == BenchmarkOutcome.PASS:
78+
passed += 1
79+
elif result.outcome == BenchmarkOutcome.FAIL:
80+
failed += 1
81+
elif result.outcome == BenchmarkOutcome.ERROR:
82+
errored += 1
83+
elif result.outcome == BenchmarkOutcome.SKIPPED:
84+
skipped += 1
85+
86+
average_duration_ms = 0.0 if total == 0 else duration_sum / float(total)
87+
88+
return BenchmarkSummary(
89+
total=total,
90+
passed=passed,
91+
failed=failed,
92+
errored=errored,
93+
skipped=skipped,
94+
average_duration_ms=average_duration_ms,
95+
by_domain=by_domain,
96+
by_outcome=by_outcome,
97+
)
98+
99+
100+
def results_by_domain(results: Iterable[BenchmarkResult]) -> dict[str, list[BenchmarkResult]]:
101+
"""
102+
Group benchmark results by domain string.
103+
"""
104+
grouped: dict[str, list[BenchmarkResult]] = {}
105+
for result in results:
106+
grouped.setdefault(result.domain.value, []).append(result)
107+
return grouped
108+
109+
110+
def results_by_outcome(results: Iterable[BenchmarkResult]) -> dict[str, list[BenchmarkResult]]:
111+
"""
112+
Group benchmark results by outcome string.
113+
"""
114+
grouped: dict[str, list[BenchmarkResult]] = {}
115+
for result in results:
116+
grouped.setdefault(result.outcome.value, []).append(result)
117+
return grouped
118+
119+
120+
def domain_pass_rate(
121+
results: Iterable[BenchmarkResult],
122+
*,
123+
domain: BenchmarkDomain | str,
124+
) -> float:
125+
"""
126+
Compute pass rate for one domain as a fraction in [0, 1].
127+
128+
Returns 0.0 when there are no results for the requested domain.
129+
"""
130+
domain_value = domain.value if isinstance(domain, BenchmarkDomain) else str(domain)
131+
filtered = [result for result in results if result.domain.value == domain_value]
132+
if not filtered:
133+
return 0.0
134+
135+
passed = sum(1 for result in filtered if result.outcome == BenchmarkOutcome.PASS)
136+
return passed / float(len(filtered))
137+
138+
139+
def export_results(results: Iterable[BenchmarkResult]) -> list[dict[str, object]]:
140+
"""
141+
Export a benchmark result set as a list of dictionaries.
142+
"""
143+
return [result.to_dict() for result in results]
144+
145+
146+
__all__ = [
147+
"BenchmarkSummary",
148+
"summarize_results",
149+
"results_by_domain",
150+
"results_by_outcome",
151+
"domain_pass_rate",
152+
"export_results",
153+
]

0 commit comments

Comments
 (0)