Coverage for src / tracekit / reporting / content / minimal.py: 95%
76 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-11 23:04 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-11 23:04 +0000
1"""Minimal boilerplate content generation.
3This module eliminates unnecessary static text and focuses on data-driven
4narrative with compact formatting and automated captions.
7Example:
8 >>> from tracekit.reporting.content import generate_compact_text
9 >>> compact = generate_compact_text(value=2.3e-9, spec=5e-9, unit="s")
10 >>> print(compact) # "Rise time: 2.3ns (spec <5ns, ✓ 54% margin)"
12References:
13"""
15from __future__ import annotations
17from dataclasses import dataclass
18from typing import Any, Literal
21@dataclass
22class MinimalContent:
23 """Minimal boilerplate content generator.
25 Focuses on data-driven content with minimal filler text.
27 Attributes:
28 auto_units: Auto-scale units (2300ns → 2.3μs).
29 data_first: Show results before methodology.
30 show_passing: Include passing tests or violations only.
31 auto_captions: Generate captions from data, not static templates.
33 References:
34 REPORT-003: Minimal Boilerplate Content
35 """
37 auto_units: bool = True
38 data_first: bool = True
39 show_passing: bool = True
40 auto_captions: bool = True
43def generate_compact_text(
44 value: float,
45 spec: float | None = None,
46 unit: str = "",
47 *,
48 spec_type: Literal["max", "min"] = "max",
49 name: str = "",
50) -> str:
51 """Generate compact, data-driven text.
53 Avoids filler text like "The measurement was performed and the result was...".
54 Instead produces compact format: "Rise time: 2.3ns (spec <5ns, ✓ 54% margin)".
56 Args:
57 value: Measured value.
58 spec: Specification limit (optional).
59 unit: Unit string.
60 spec_type: Type of specification (max or min).
61 name: Measurement name (optional).
63 Returns:
64 Compact formatted string.
66 Example:
67 >>> generate_compact_text(2.3e-9, 5e-9, "s", name="Rise time")
68 'Rise time: 2.3ns (spec <5ns, ✓ 54% margin)'
70 References:
71 REPORT-003: Minimal Boilerplate Content
72 """
73 from tracekit.reporting.formatting.numbers import format_with_units
75 # Format value with auto-scaled units
76 value_str = format_with_units(value, unit)
78 # Build compact text
79 parts = []
80 if name:
81 parts.append(f"{name}:")
83 parts.append(value_str)
85 # Add spec context if provided
86 if spec is not None:
87 spec_str = format_with_units(spec, unit)
88 spec_symbol = "<" if spec_type == "max" else ">"
90 # Calculate pass/fail and margin
91 if spec_type == "max":
92 passed = value <= spec
93 margin = (spec - value) / spec * 100 if spec != 0 else 0
94 else:
95 passed = value >= spec
96 margin = (value - spec) / spec * 100 if spec != 0 else 0
98 status_symbol = "\u2713" if passed else "\u2717" # ✓ or ✗
99 spec_part = f"(spec {spec_symbol}{spec_str}, {status_symbol} {margin:.0f}% margin)"
100 parts.append(spec_part)
102 return " ".join(parts)
105def auto_caption(
106 data_type: str,
107 data: dict[str, Any],
108 *,
109 include_stats: bool = True,
110) -> str:
111 """Generate automated captions from data.
113 Instead of static templates, generates captions based on actual data content.
115 Args:
116 data_type: Type of data (measurement, plot, table).
117 data: Data dictionary.
118 include_stats: Include statistics in caption.
120 Returns:
121 Generated caption string.
123 Example:
124 >>> data = {"name": "Rise time", "count": 100, "mean": 2.3e-9}
125 >>> auto_caption("measurement", data)
126 'Rise time measurement (n=100, mean=2.3ns)'
128 References:
129 REPORT-003: Minimal Boilerplate Content
130 """
131 parts = []
133 # Extract key information
134 name = data.get("name", data_type.title())
135 parts.append(name)
137 # Add data-specific information
138 if data_type == "measurement" and include_stats:
139 count = data.get("count")
140 if count:
141 parts.append(f"(n={count}")
143 mean = data.get("mean")
144 if mean is not None:
145 from tracekit.reporting.formatting.numbers import format_with_units
147 unit = data.get("unit", "")
148 mean_str = format_with_units(mean, unit)
149 parts[-1] += f", mean={mean_str}"
151 parts[-1] += ")"
153 elif data_type == "plot":
154 plot_type = data.get("type", "plot")
155 if plot_type != "plot":
156 parts.append(f"- {plot_type}")
158 elif data_type == "table":
159 rows = data.get("rows")
160 cols = data.get("cols")
161 if rows and cols:
162 parts.append(f"({rows}x{cols})")
164 return " ".join(parts)
167def remove_filler_text(text: str) -> str:
168 """Remove common filler phrases from text.
170 Args:
171 text: Input text.
173 Returns:
174 Text with filler removed.
176 Example:
177 >>> text = "The measurement was performed and the result was 2.3ns."
178 >>> remove_filler_text(text)
179 'Result: 2.3ns.'
181 References:
182 REPORT-003: Minimal Boilerplate Content
183 """
184 # Common filler phrases to remove
185 filler_phrases = [
186 "The measurement was performed and",
187 "The result was",
188 "It was found that",
189 "The analysis shows that",
190 "It can be seen that",
191 "As can be observed",
192 "The data indicates",
193 "It should be noted that",
194 ]
196 result = text
197 for phrase in filler_phrases:
198 result = result.replace(phrase, "").strip()
200 # Clean up extra spaces
201 while " " in result:
202 result = result.replace(" ", " ")
204 # Capitalize first letter after removal
205 if result and not result[0].isupper():
206 result = result[0].upper() + result[1:]
208 return result
211def conditional_section(
212 data: list[Any] | dict[str, Any],
213 section_title: str,
214) -> tuple[bool, str]:
215 """Determine if section should be shown.
217 Only show sections if data exists (no empty sections).
219 Args:
220 data: Section data.
221 section_title: Section title.
223 Returns:
224 Tuple of (should_show, reason).
226 Example:
227 >>> should_show, reason = conditional_section([], "Violations")
228 >>> print(should_show) # False
229 >>> print(reason) # "No violations found"
231 References:
232 REPORT-003: Minimal Boilerplate Content
233 """
234 if isinstance(data, list):
235 if not data:
236 return False, f"No {section_title.lower()} found"
237 return True, f"{len(data)} {section_title.lower()}"
239 elif isinstance(data, dict): 239 ↛ 246line 239 didn't jump to line 246 because the condition on line 239 was always true
240 if not data or all(not v for v in data.values()):
241 return False, f"No {section_title.lower()} found"
242 return True, ""
244 else:
245 # For other types, check if truthy
246 if not data: # type: ignore[unreachable]
247 return False, f"No {section_title.lower()} found"
248 return True, ""
251__all__ = [
252 "MinimalContent",
253 "auto_caption",
254 "conditional_section",
255 "generate_compact_text",
256 "remove_filler_text",
257]