Coverage for src / tracekit / reporting / sections.py: 99%

153 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-01-11 23:04 +0000

1"""Report section generation for TraceKit. 

2 

3This module provides utilities for creating standardized report sections 

4including title, summary, measurements, plots, and conclusions. 

5 

6 

7Example: 

8 >>> from tracekit.reporting.sections import create_title_section 

9 >>> section = create_title_section("Signal Analysis Report", author="Engineer") 

10""" 

11 

12from __future__ import annotations 

13 

14from datetime import datetime 

15from typing import Any 

16 

17from tracekit.reporting.core import Section 

18 

19 

20def create_title_section( 

21 title: str, 

22 *, 

23 author: str | None = None, 

24 date: datetime | None = None, 

25 subtitle: str | None = None, 

26) -> Section: 

27 """Create title section for report. 

28 

29 Args: 

30 title: Report title. 

31 author: Report author. 

32 date: Report date (defaults to now). 

33 subtitle: Optional subtitle. 

34 

35 Returns: 

36 Title Section object. 

37 

38 References: 

39 REPORT-006 

40 """ 

41 content_parts = [] 

42 

43 if subtitle: 

44 content_parts.append(subtitle) 

45 

46 if author: 

47 content_parts.append(f"Author: {author}") 

48 

49 if date is None: 

50 date = datetime.now() 

51 content_parts.append(f"Date: {date.strftime('%Y-%m-%d %H:%M')}") 

52 

53 content = "\n".join(content_parts) 

54 

55 return Section( 

56 title=title, 

57 content=content, 

58 level=0, # Top level 

59 visible=True, 

60 ) 

61 

62 

63def create_executive_summary_section( 

64 results: dict[str, Any], 

65 *, 

66 key_findings: list[str] | None = None, 

67 length: str = "summary", 

68) -> Section: 

69 """Create executive summary section. 

70 

71 Args: 

72 results: Analysis results dictionary. 

73 key_findings: List of key findings to highlight. 

74 length: Summary length (short, summary, detailed). 

75 

76 Returns: 

77 Executive Summary Section object. 

78 

79 References: 

80 REPORT-004, REPORT-006 

81 """ 

82 content_parts = [] 

83 

84 # Overall status 

85 if "pass_count" in results and "total_count" in results: 

86 pass_count = results["pass_count"] 

87 total = results["total_count"] 

88 

89 if pass_count == total: 

90 content_parts.append(f"All {total} tests passed with satisfactory margins.") 

91 else: 

92 fail_count = total - pass_count 

93 content_parts.append( 

94 f"{fail_count} of {total} tests failed ({fail_count / total * 100:.0f}% failure rate)." 

95 ) 

96 

97 # Key findings 

98 if key_findings: 

99 content_parts.append("\n**Key Findings:**") 

100 for finding in key_findings[:5]: # Top 5 

101 content_parts.append(f"- {finding}") 

102 

103 # Margin analysis 

104 if "min_margin" in results: 

105 margin = results["min_margin"] 

106 content_parts.append("\n**Margin Analysis:**") 

107 if margin < 0: 

108 content_parts.append(f"Critical: Minimum margin is {margin:.1f}% (violation).") 

109 elif margin < 10: 

110 content_parts.append( 

111 f"Warning: Minimum margin is {margin:.1f}% (below recommended 10%)." 

112 ) 

113 elif margin < 20: 

114 content_parts.append(f"Acceptable: Minimum margin is {margin:.1f}% (below target 20%).") 

115 else: 

116 content_parts.append(f"Good: Minimum margin is {margin:.1f}% (exceeds target 20%).") 

117 

118 # Recommendations (for detailed summary) 

119 if length == "detailed" and "violations" in results: 

120 violations = results["violations"] 

121 if violations: 121 ↛ 128line 121 didn't jump to line 128 because the condition on line 121 was always true

122 content_parts.append("\n**Recommendations:**") 

123 for violation in violations[:3]: 

124 content_parts.append( 

125 f"- Address {violation.get('parameter', 'measurement')} violation" 

126 ) 

127 

128 content = "\n".join(content_parts) 

129 

130 return Section( 

131 title="Executive Summary", 

132 content=content, 

133 level=1, 

134 visible=True, 

135 ) 

136 

137 

138def create_measurement_results_section( 

139 measurements: dict[str, Any], 

140 *, 

141 include_plots: bool = False, 

142) -> Section: 

143 """Create measurement results section. 

144 

145 Args: 

146 measurements: Dictionary of measurement results. 

147 include_plots: Include measurement plots. 

148 

149 Returns: 

150 Measurement Results Section object. 

151 

152 References: 

153 REPORT-006 

154 """ 

155 from tracekit.reporting.tables import create_measurement_table 

156 

157 # Create measurement table 

158 table = create_measurement_table(measurements, format="dict") 

159 

160 content = [table] 

161 

162 # Add interpretation if any failures 

163 failed_count = sum(1 for m in measurements.values() if not m.get("passed", True)) 

164 if failed_count > 0: 

165 interpretation = f"\n{failed_count} measurement(s) failed specification limits." 

166 content.insert(0, interpretation) 

167 

168 return Section( 

169 title="Measurement Results", 

170 content=content, 

171 level=1, 

172 visible=True, 

173 ) 

174 

175 

176def create_plots_section( 

177 figures: list[dict[str, Any]], 

178) -> Section: 

179 """Create plots section. 

180 

181 Args: 

182 figures: List of figure dictionaries. 

183 

184 Returns: 

185 Plots Section object. 

186 

187 References: 

188 REPORT-006 

189 """ 

190 content = [] 

191 

192 for fig in figures: 

193 content.append(fig) 

194 

195 return Section( 

196 title="Waveform Plots", 

197 content=content, 

198 level=1, 

199 visible=True, 

200 ) 

201 

202 

203def create_methodology_section( 

204 analysis_params: dict[str, Any], 

205 *, 

206 verbosity: str = "standard", 

207) -> Section: 

208 """Create methodology section. 

209 

210 Args: 

211 analysis_params: Analysis parameters and settings. 

212 verbosity: Detail level (summary, standard, detailed). 

213 

214 Returns: 

215 Methodology Section object. 

216 

217 References: 

218 REPORT-006 

219 """ 

220 content_parts = [] 

221 

222 # Test setup 

223 if "sample_rate" in analysis_params: 

224 content_parts.append(f"Sample rate: {analysis_params['sample_rate']:.3g} Hz") 

225 if "num_samples" in analysis_params: 

226 content_parts.append(f"Number of samples: {analysis_params['num_samples']:,}") 

227 if "duration" in analysis_params: 

228 content_parts.append(f"Capture duration: {analysis_params['duration']:.6g} s") 

229 

230 # Analysis methods 

231 if verbosity in ("standard", "detailed"): 

232 content_parts.append("\n**Analysis Methods:**") 

233 methods = analysis_params.get("methods", []) 

234 if methods: 

235 for method in methods: 

236 content_parts.append(f"- {method}") 

237 else: 

238 content_parts.append("- Standard signal analysis algorithms") 

239 

240 # Standards compliance 

241 if "standards" in analysis_params: 

242 content_parts.append("\n**Standards:**") 

243 for standard in analysis_params["standards"]: 

244 content_parts.append(f"- {standard}") 

245 

246 # Detailed parameters 

247 if verbosity == "detailed": 

248 content_parts.append("\n**Detailed Parameters:**") 

249 for key, value in analysis_params.items(): 

250 if key not in ( 

251 "sample_rate", 

252 "num_samples", 

253 "duration", 

254 "methods", 

255 "standards", 

256 ): 

257 content_parts.append(f"- {key}: {value}") 

258 

259 content = "\n".join(content_parts) 

260 

261 return Section( 

262 title="Methodology", 

263 content=content, 

264 level=1, 

265 visible=True, 

266 collapsible=True, 

267 ) 

268 

269 

270def create_conclusions_section( 

271 results: dict[str, Any], 

272 *, 

273 recommendations: list[str] | None = None, 

274) -> Section: 

275 """Create conclusions section. 

276 

277 Args: 

278 results: Analysis results. 

279 recommendations: List of recommendations. 

280 

281 Returns: 

282 Conclusions Section object. 

283 

284 References: 

285 REPORT-006 

286 """ 

287 content_parts = [] 

288 

289 # Overall conclusion 

290 if "pass_count" in results and "total_count" in results: 

291 pass_count = results["pass_count"] 

292 total = results["total_count"] 

293 

294 if pass_count == total: 

295 content_parts.append( 

296 "The device under test meets all specifications and is ready for deployment." 

297 ) 

298 else: 

299 fail_count = total - pass_count 

300 content_parts.append( 

301 f"The device under test has {fail_count} specification violation(s) " 

302 "that must be addressed before deployment." 

303 ) 

304 

305 # Risk assessment 

306 if "min_margin" in results: 

307 margin = results["min_margin"] 

308 content_parts.append("\n**Risk Assessment:**") 

309 if margin < 0: 

310 content_parts.append("HIGH RISK: Specification violations detected.") 

311 elif margin < 10: 

312 content_parts.append("MEDIUM RISK: Insufficient design margin.") 

313 elif margin < 20: 

314 content_parts.append("LOW RISK: Adequate margin but below target.") 

315 else: 

316 content_parts.append("ACCEPTABLE: Sufficient design margin.") 

317 

318 # Recommendations 

319 if recommendations: 

320 content_parts.append("\n**Recommendations:**") 

321 for rec in recommendations: 

322 content_parts.append(f"- {rec}") 

323 

324 content = "\n".join(content_parts) 

325 

326 return Section( 

327 title="Conclusions", 

328 content=content, 

329 level=1, 

330 visible=True, 

331 ) 

332 

333 

334def create_appendix_section( 

335 raw_data: dict[str, Any], 

336 *, 

337 include_provenance: bool = True, 

338) -> Section: 

339 """Create appendix section with raw data and provenance. 

340 

341 Args: 

342 raw_data: Raw data to include. 

343 include_provenance: Include data provenance information. 

344 

345 Returns: 

346 Appendix Section object. 

347 

348 References: 

349 REPORT-006 

350 """ 

351 content_parts = [] 

352 

353 # Provenance 

354 if include_provenance: 

355 content_parts.append("**Data Provenance:**") 

356 if "source_file" in raw_data: 

357 content_parts.append(f"Source: {raw_data['source_file']}") 

358 if "timestamp" in raw_data: 

359 content_parts.append(f"Timestamp: {raw_data['timestamp']}") 

360 if "tool_version" in raw_data: 

361 content_parts.append(f"Tool Version: {raw_data['tool_version']}") 

362 

363 # Raw data (truncated) 

364 content_parts.append("\n**Raw Data:** (See detailed output for full data)") 

365 

366 content = "\n".join(content_parts) 

367 

368 return Section( 

369 title="Appendix", 

370 content=content, 

371 level=1, 

372 visible=True, 

373 collapsible=True, 

374 ) 

375 

376 

377def create_violations_section( 

378 violations: list[dict[str, Any]], 

379) -> Section: 

380 """Create violations section highlighting failures. 

381 

382 Args: 

383 violations: List of violation dictionaries. 

384 

385 Returns: 

386 Violations Section object. 

387 

388 References: 

389 REPORT-005 (Smart Content Filtering) 

390 """ 

391 if not violations: 

392 return Section( 

393 title="Violations", 

394 content="No specification violations detected.", 

395 level=1, 

396 visible=False, # Hide if no violations 

397 ) 

398 

399 content_parts = [f"**{len(violations)} specification violation(s) detected:**\n"] 

400 

401 for v in violations: 

402 param = v.get("parameter", "Unknown") 

403 value = v.get("value", "N/A") 

404 spec = v.get("specification", "N/A") 

405 severity = v.get("severity", "WARNING") 

406 

407 content_parts.append(f"- **{param}**: {value} (spec: {spec}) [{severity}]") 

408 

409 content = "\n".join(content_parts) 

410 

411 return Section( 

412 title="Violations", 

413 content=content, 

414 level=1, 

415 visible=True, 

416 ) 

417 

418 

419def create_standard_report_sections( 

420 results: dict[str, Any], 

421 *, 

422 verbosity: str = "standard", 

423) -> list[Section]: 

424 """Create standard set of report sections. 

425 

426 Args: 

427 results: Complete analysis results. 

428 verbosity: Report verbosity level. 

429 

430 Returns: 

431 List of Section objects for a complete report. 

432 

433 References: 

434 REPORT-006 

435 """ 

436 sections = [] 

437 

438 # Executive summary (all verbosity levels) 

439 if "summary" in results or "pass_count" in results: 

440 sections.append(create_executive_summary_section(results)) 

441 

442 # Violations (if any) 

443 if results.get("violations"): 

444 sections.append(create_violations_section(results["violations"])) 

445 

446 # Measurement results 

447 if "measurements" in results: 

448 sections.append(create_measurement_results_section(results["measurements"])) 

449 

450 # Plots (if available) 

451 if results.get("figures"): 

452 sections.append(create_plots_section(results["figures"])) 

453 

454 # Methodology (standard and above) 

455 if verbosity in ("standard", "detailed", "debug") and "analysis_params" in results: 

456 sections.append(create_methodology_section(results["analysis_params"], verbosity=verbosity)) 

457 

458 # Conclusions 

459 if "conclusions" in results or "recommendations" in results: 

460 sections.append( 

461 create_conclusions_section( 

462 results, 

463 recommendations=results.get("recommendations"), 

464 ) 

465 ) 

466 

467 # Appendix (detailed and debug only) 

468 if verbosity in ("detailed", "debug"): 

469 sections.append(create_appendix_section(results)) 

470 

471 return sections