Coverage for src / tracekit / ui / progressive_display.py: 100%
121 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"""Progressive disclosure UI pattern for information hierarchy.
3This module implements progressive disclosure to display simple, high-level
4results first with options to drill down into details on demand.
7Example:
8 >>> from tracekit.ui import ProgressiveDisplay
9 >>> display = ProgressiveDisplay()
10 >>> output = display.render(result)
11 >>> print(output.summary()) # Level 1: Summary
12 >>> print(output.details()) # Level 2: Intermediate
13 >>> print(output.expert()) # Level 3: Expert
15References:
16 TraceKit Auto-Discovery Specification
17"""
19from __future__ import annotations
21from dataclasses import dataclass, field
22from typing import Any
25@dataclass
26class Section:
27 """Progressive disclosure section.
29 Attributes:
30 title: Section title.
31 summary: Brief summary text.
32 content: Full content (shown when expanded).
33 visualization: Optional plot or chart.
34 is_collapsed: Whether section is initially collapsed.
35 detail_level: Minimum detail level to show (1-3).
36 """
38 title: str
39 summary: str
40 content: str = ""
41 visualization: Any = None
42 is_collapsed: bool = True
43 detail_level: int = 2
46@dataclass
47class ProgressiveOutput:
48 """Progressive disclosure output container.
50 Attributes:
51 level1_content: Summary level content (≤5 items, ≤100 words).
52 level2_sections: Intermediate level sections.
53 level3_data: Expert level data (raw values, debug info).
54 current_level: Current display level (1-3).
55 """
57 level1_content: str
58 level2_sections: list[Section] = field(default_factory=list)
59 level3_data: dict[str, Any] = field(default_factory=dict)
60 current_level: int = 1
62 def summary(self) -> str:
63 """Get Level 1 summary.
65 Returns:
66 Brief summary with 3-5 key items.
67 """
68 return self.level1_content
70 def details(self, level: str = "intermediate") -> str:
71 """Get detailed view.
73 Args:
74 level: Detail level ("intermediate" or "expert").
76 Returns:
77 Formatted details at requested level.
78 """
79 if level == "expert":
80 return self.expert()
82 # Level 2: Intermediate details
83 output = self.level1_content + "\n\n"
85 for section in self.level2_sections:
86 output += f"\n{'=' * 60}\n"
87 output += f"{section.title}\n"
88 output += f"{'=' * 60}\n"
90 if section.is_collapsed:
91 output += f"{section.summary}\n"
92 output += "[+] Expand for details\n"
93 else:
94 output += f"{section.content}\n"
96 return output
98 def expert(self) -> str:
99 """Get Level 3 expert view.
101 Returns:
102 Complete data including raw values and debug info.
103 """
104 output = self.details(level="intermediate") + "\n\n"
106 if self.level3_data:
107 output += "\n" + "=" * 60 + "\n"
108 output += "EXPERT DETAILS\n"
109 output += "=" * 60 + "\n"
111 for key, value in self.level3_data.items():
112 output += f"\n{key}:\n"
113 output += f" {value}\n"
115 return output
117 def has_level3(self) -> bool:
118 """Check if expert level data is available.
120 Returns:
121 True if level 3 data exists.
122 """
123 return bool(self.level3_data)
125 def expand_section(self, title: str) -> None:
126 """Expand a collapsed section.
128 Args:
129 title: Section title to expand.
130 """
131 for section in self.level2_sections:
132 if section.title == title:
133 section.is_collapsed = False
134 break
136 def collapse_section(self, title: str) -> None:
137 """Collapse an expanded section.
139 Args:
140 title: Section title to collapse.
141 """
142 for section in self.level2_sections:
143 if section.title == title:
144 section.is_collapsed = True
145 break
147 def export(
148 self,
149 path: str,
150 detail_level: str = "intermediate",
151 ) -> None:
152 """Export to file with specified detail level.
154 Args:
155 path: Output file path.
156 detail_level: Detail level ("summary", "intermediate", "expert").
157 """
158 if detail_level == "summary":
159 content = self.summary()
160 elif detail_level == "expert":
161 content = self.expert()
162 else:
163 content = self.details()
165 with open(path, "w") as f:
166 f.write(content)
169class ProgressiveDisplay:
170 """Progressive disclosure display manager.
172 Manages hierarchical display of information with three detail levels:
173 - L1: Summary (≤5 items, ≤100 words)
174 - L2: Intermediate (≤20 items, includes charts)
175 - L3: Expert (full data, raw values, debug info)
177 Example:
178 >>> display = ProgressiveDisplay(default_level="summary")
179 >>> output = display.render(analysis_result)
180 >>> print(output.summary()) # Simple overview
181 >>> output.expand_section("Timing Analysis")
182 >>> print(output.details()) # More detail
184 References:
185 DISC-011: Progressive Disclosure
186 """
188 def __init__(
189 self,
190 default_level: str = "summary",
191 max_summary_items: int = 5,
192 enable_collapsible_sections: bool = True,
193 ):
194 """Initialize progressive display manager.
196 Args:
197 default_level: Default detail level ("summary", "intermediate", "expert").
198 max_summary_items: Maximum items in summary (3-10).
199 enable_collapsible_sections: Enable section collapse/expand.
200 """
201 self.default_level = default_level
202 self.max_summary_items = max(3, min(10, max_summary_items))
203 self.enable_collapsible_sections = enable_collapsible_sections
205 def render(self, result: Any) -> ProgressiveOutput:
206 """Render result with progressive disclosure.
208 Args:
209 result: Analysis result to render.
211 Returns:
212 ProgressiveOutput with hierarchical content.
214 Example:
215 >>> output = display.render(characterization_result)
216 >>> print(output.summary())
217 """
218 # Level 1: Build summary (3-5 key items)
219 level1_items = []
221 if hasattr(result, "signal_type"):
222 level1_items.append(f"Signal Type: {result.signal_type}")
224 if hasattr(result, "confidence"):
225 confidence_pct = result.confidence * 100
226 level1_items.append(f"Confidence: {confidence_pct:.0f}%")
228 if hasattr(result, "quality"):
229 level1_items.append(f"Quality: {result.quality}")
231 if hasattr(result, "status"):
232 level1_items.append(f"Status: {result.status}")
234 # Limit to max_summary_items
235 level1_items = level1_items[: self.max_summary_items]
237 level1_content = "\n".join(level1_items)
239 if not level1_items:
240 level1_content = "Analysis complete. Expand for details."
242 # Level 2: Build sections
243 level2_sections = []
245 # Parameters section
246 if hasattr(result, "parameters") and result.parameters:
247 params = result.parameters
248 summary = f"{len(params)} parameters detected"
250 content = "Parameters:\n"
251 for key, value in params.items():
252 content += f" {key}: {value}\n"
254 level2_sections.append(
255 Section(
256 title="Parameters",
257 summary=summary,
258 content=content,
259 is_collapsed=self.enable_collapsible_sections,
260 detail_level=2,
261 )
262 )
264 # Quality metrics section
265 if hasattr(result, "quality") or hasattr(result, "metrics"):
266 metrics = getattr(result, "metrics", {})
268 summary = "Quality assessment available"
269 content = "Quality Metrics:\n"
271 if hasattr(result, "quality"):
272 content += f" Overall: {result.quality}\n"
274 if metrics:
275 for key, value in metrics.items():
276 content += f" {key}: {value}\n"
278 level2_sections.append(
279 Section(
280 title="Quality Metrics",
281 summary=summary,
282 content=content,
283 is_collapsed=self.enable_collapsible_sections,
284 detail_level=2,
285 )
286 )
288 # Findings section
289 if hasattr(result, "findings") and result.findings:
290 findings = result.findings
292 summary = f"{len(findings)} findings"
293 content = "Findings:\n"
295 for i, finding in enumerate(findings, 1):
296 if hasattr(finding, "title") and hasattr(finding, "description"):
297 content += f"\n{i}. {finding.title}\n"
298 content += f" {finding.description}\n"
299 else:
300 content += f"\n{i}. {finding}\n"
302 level2_sections.append(
303 Section(
304 title="Findings",
305 summary=summary,
306 content=content,
307 is_collapsed=self.enable_collapsible_sections,
308 detail_level=2,
309 )
310 )
312 # Level 3: Build expert data
313 level3_data = {}
315 if hasattr(result, "raw_data"):
316 level3_data["raw_data"] = result.raw_data
318 if hasattr(result, "algorithm_config"):
319 level3_data["algorithm_config"] = result.algorithm_config
321 if hasattr(result, "debug_trace"):
322 level3_data["debug_trace"] = result.debug_trace
324 # Determine current level
325 level_map = {"summary": 1, "intermediate": 2, "expert": 3}
326 current_level = level_map.get(self.default_level, 1)
328 return ProgressiveOutput(
329 level1_content=level1_content,
330 level2_sections=level2_sections,
331 level3_data=level3_data,
332 current_level=current_level,
333 )
336__all__ = [
337 "ProgressiveDisplay",
338 "ProgressiveOutput",
339 "Section",
340]