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

1"""Progressive disclosure UI pattern for information hierarchy. 

2 

3This module implements progressive disclosure to display simple, high-level 

4results first with options to drill down into details on demand. 

5 

6 

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 

14 

15References: 

16 TraceKit Auto-Discovery Specification 

17""" 

18 

19from __future__ import annotations 

20 

21from dataclasses import dataclass, field 

22from typing import Any 

23 

24 

25@dataclass 

26class Section: 

27 """Progressive disclosure section. 

28 

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 """ 

37 

38 title: str 

39 summary: str 

40 content: str = "" 

41 visualization: Any = None 

42 is_collapsed: bool = True 

43 detail_level: int = 2 

44 

45 

46@dataclass 

47class ProgressiveOutput: 

48 """Progressive disclosure output container. 

49 

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 """ 

56 

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 

61 

62 def summary(self) -> str: 

63 """Get Level 1 summary. 

64 

65 Returns: 

66 Brief summary with 3-5 key items. 

67 """ 

68 return self.level1_content 

69 

70 def details(self, level: str = "intermediate") -> str: 

71 """Get detailed view. 

72 

73 Args: 

74 level: Detail level ("intermediate" or "expert"). 

75 

76 Returns: 

77 Formatted details at requested level. 

78 """ 

79 if level == "expert": 

80 return self.expert() 

81 

82 # Level 2: Intermediate details 

83 output = self.level1_content + "\n\n" 

84 

85 for section in self.level2_sections: 

86 output += f"\n{'=' * 60}\n" 

87 output += f"{section.title}\n" 

88 output += f"{'=' * 60}\n" 

89 

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" 

95 

96 return output 

97 

98 def expert(self) -> str: 

99 """Get Level 3 expert view. 

100 

101 Returns: 

102 Complete data including raw values and debug info. 

103 """ 

104 output = self.details(level="intermediate") + "\n\n" 

105 

106 if self.level3_data: 

107 output += "\n" + "=" * 60 + "\n" 

108 output += "EXPERT DETAILS\n" 

109 output += "=" * 60 + "\n" 

110 

111 for key, value in self.level3_data.items(): 

112 output += f"\n{key}:\n" 

113 output += f" {value}\n" 

114 

115 return output 

116 

117 def has_level3(self) -> bool: 

118 """Check if expert level data is available. 

119 

120 Returns: 

121 True if level 3 data exists. 

122 """ 

123 return bool(self.level3_data) 

124 

125 def expand_section(self, title: str) -> None: 

126 """Expand a collapsed section. 

127 

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 

135 

136 def collapse_section(self, title: str) -> None: 

137 """Collapse an expanded section. 

138 

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 

146 

147 def export( 

148 self, 

149 path: str, 

150 detail_level: str = "intermediate", 

151 ) -> None: 

152 """Export to file with specified detail level. 

153 

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

164 

165 with open(path, "w") as f: 

166 f.write(content) 

167 

168 

169class ProgressiveDisplay: 

170 """Progressive disclosure display manager. 

171 

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) 

176 

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 

183 

184 References: 

185 DISC-011: Progressive Disclosure 

186 """ 

187 

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. 

195 

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 

204 

205 def render(self, result: Any) -> ProgressiveOutput: 

206 """Render result with progressive disclosure. 

207 

208 Args: 

209 result: Analysis result to render. 

210 

211 Returns: 

212 ProgressiveOutput with hierarchical content. 

213 

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 = [] 

220 

221 if hasattr(result, "signal_type"): 

222 level1_items.append(f"Signal Type: {result.signal_type}") 

223 

224 if hasattr(result, "confidence"): 

225 confidence_pct = result.confidence * 100 

226 level1_items.append(f"Confidence: {confidence_pct:.0f}%") 

227 

228 if hasattr(result, "quality"): 

229 level1_items.append(f"Quality: {result.quality}") 

230 

231 if hasattr(result, "status"): 

232 level1_items.append(f"Status: {result.status}") 

233 

234 # Limit to max_summary_items 

235 level1_items = level1_items[: self.max_summary_items] 

236 

237 level1_content = "\n".join(level1_items) 

238 

239 if not level1_items: 

240 level1_content = "Analysis complete. Expand for details." 

241 

242 # Level 2: Build sections 

243 level2_sections = [] 

244 

245 # Parameters section 

246 if hasattr(result, "parameters") and result.parameters: 

247 params = result.parameters 

248 summary = f"{len(params)} parameters detected" 

249 

250 content = "Parameters:\n" 

251 for key, value in params.items(): 

252 content += f" {key}: {value}\n" 

253 

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 ) 

263 

264 # Quality metrics section 

265 if hasattr(result, "quality") or hasattr(result, "metrics"): 

266 metrics = getattr(result, "metrics", {}) 

267 

268 summary = "Quality assessment available" 

269 content = "Quality Metrics:\n" 

270 

271 if hasattr(result, "quality"): 

272 content += f" Overall: {result.quality}\n" 

273 

274 if metrics: 

275 for key, value in metrics.items(): 

276 content += f" {key}: {value}\n" 

277 

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 ) 

287 

288 # Findings section 

289 if hasattr(result, "findings") and result.findings: 

290 findings = result.findings 

291 

292 summary = f"{len(findings)} findings" 

293 content = "Findings:\n" 

294 

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" 

301 

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 ) 

311 

312 # Level 3: Build expert data 

313 level3_data = {} 

314 

315 if hasattr(result, "raw_data"): 

316 level3_data["raw_data"] = result.raw_data 

317 

318 if hasattr(result, "algorithm_config"): 

319 level3_data["algorithm_config"] = result.algorithm_config 

320 

321 if hasattr(result, "debug_trace"): 

322 level3_data["debug_trace"] = result.debug_trace 

323 

324 # Determine current level 

325 level_map = {"summary": 1, "intermediate": 2, "expert": 3} 

326 current_level = level_map.get(self.default_level, 1) 

327 

328 return ProgressiveOutput( 

329 level1_content=level1_content, 

330 level2_sections=level2_sections, 

331 level3_data=level3_data, 

332 current_level=current_level, 

333 ) 

334 

335 

336__all__ = [ 

337 "ProgressiveDisplay", 

338 "ProgressiveOutput", 

339 "Section", 

340]