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
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-11 23:04 +0000
1"""Report section generation for TraceKit.
3This module provides utilities for creating standardized report sections
4including title, summary, measurements, plots, and conclusions.
7Example:
8 >>> from tracekit.reporting.sections import create_title_section
9 >>> section = create_title_section("Signal Analysis Report", author="Engineer")
10"""
12from __future__ import annotations
14from datetime import datetime
15from typing import Any
17from tracekit.reporting.core import Section
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.
29 Args:
30 title: Report title.
31 author: Report author.
32 date: Report date (defaults to now).
33 subtitle: Optional subtitle.
35 Returns:
36 Title Section object.
38 References:
39 REPORT-006
40 """
41 content_parts = []
43 if subtitle:
44 content_parts.append(subtitle)
46 if author:
47 content_parts.append(f"Author: {author}")
49 if date is None:
50 date = datetime.now()
51 content_parts.append(f"Date: {date.strftime('%Y-%m-%d %H:%M')}")
53 content = "\n".join(content_parts)
55 return Section(
56 title=title,
57 content=content,
58 level=0, # Top level
59 visible=True,
60 )
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.
71 Args:
72 results: Analysis results dictionary.
73 key_findings: List of key findings to highlight.
74 length: Summary length (short, summary, detailed).
76 Returns:
77 Executive Summary Section object.
79 References:
80 REPORT-004, REPORT-006
81 """
82 content_parts = []
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"]
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 )
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}")
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%).")
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 )
128 content = "\n".join(content_parts)
130 return Section(
131 title="Executive Summary",
132 content=content,
133 level=1,
134 visible=True,
135 )
138def create_measurement_results_section(
139 measurements: dict[str, Any],
140 *,
141 include_plots: bool = False,
142) -> Section:
143 """Create measurement results section.
145 Args:
146 measurements: Dictionary of measurement results.
147 include_plots: Include measurement plots.
149 Returns:
150 Measurement Results Section object.
152 References:
153 REPORT-006
154 """
155 from tracekit.reporting.tables import create_measurement_table
157 # Create measurement table
158 table = create_measurement_table(measurements, format="dict")
160 content = [table]
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)
168 return Section(
169 title="Measurement Results",
170 content=content,
171 level=1,
172 visible=True,
173 )
176def create_plots_section(
177 figures: list[dict[str, Any]],
178) -> Section:
179 """Create plots section.
181 Args:
182 figures: List of figure dictionaries.
184 Returns:
185 Plots Section object.
187 References:
188 REPORT-006
189 """
190 content = []
192 for fig in figures:
193 content.append(fig)
195 return Section(
196 title="Waveform Plots",
197 content=content,
198 level=1,
199 visible=True,
200 )
203def create_methodology_section(
204 analysis_params: dict[str, Any],
205 *,
206 verbosity: str = "standard",
207) -> Section:
208 """Create methodology section.
210 Args:
211 analysis_params: Analysis parameters and settings.
212 verbosity: Detail level (summary, standard, detailed).
214 Returns:
215 Methodology Section object.
217 References:
218 REPORT-006
219 """
220 content_parts = []
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")
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")
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}")
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}")
259 content = "\n".join(content_parts)
261 return Section(
262 title="Methodology",
263 content=content,
264 level=1,
265 visible=True,
266 collapsible=True,
267 )
270def create_conclusions_section(
271 results: dict[str, Any],
272 *,
273 recommendations: list[str] | None = None,
274) -> Section:
275 """Create conclusions section.
277 Args:
278 results: Analysis results.
279 recommendations: List of recommendations.
281 Returns:
282 Conclusions Section object.
284 References:
285 REPORT-006
286 """
287 content_parts = []
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"]
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 )
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.")
318 # Recommendations
319 if recommendations:
320 content_parts.append("\n**Recommendations:**")
321 for rec in recommendations:
322 content_parts.append(f"- {rec}")
324 content = "\n".join(content_parts)
326 return Section(
327 title="Conclusions",
328 content=content,
329 level=1,
330 visible=True,
331 )
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.
341 Args:
342 raw_data: Raw data to include.
343 include_provenance: Include data provenance information.
345 Returns:
346 Appendix Section object.
348 References:
349 REPORT-006
350 """
351 content_parts = []
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']}")
363 # Raw data (truncated)
364 content_parts.append("\n**Raw Data:** (See detailed output for full data)")
366 content = "\n".join(content_parts)
368 return Section(
369 title="Appendix",
370 content=content,
371 level=1,
372 visible=True,
373 collapsible=True,
374 )
377def create_violations_section(
378 violations: list[dict[str, Any]],
379) -> Section:
380 """Create violations section highlighting failures.
382 Args:
383 violations: List of violation dictionaries.
385 Returns:
386 Violations Section object.
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 )
399 content_parts = [f"**{len(violations)} specification violation(s) detected:**\n"]
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")
407 content_parts.append(f"- **{param}**: {value} (spec: {spec}) [{severity}]")
409 content = "\n".join(content_parts)
411 return Section(
412 title="Violations",
413 content=content,
414 level=1,
415 visible=True,
416 )
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.
426 Args:
427 results: Complete analysis results.
428 verbosity: Report verbosity level.
430 Returns:
431 List of Section objects for a complete report.
433 References:
434 REPORT-006
435 """
436 sections = []
438 # Executive summary (all verbosity levels)
439 if "summary" in results or "pass_count" in results:
440 sections.append(create_executive_summary_section(results))
442 # Violations (if any)
443 if results.get("violations"):
444 sections.append(create_violations_section(results["violations"]))
446 # Measurement results
447 if "measurements" in results:
448 sections.append(create_measurement_results_section(results["measurements"]))
450 # Plots (if available)
451 if results.get("figures"):
452 sections.append(create_plots_section(results["figures"]))
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))
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 )
467 # Appendix (detailed and debug only)
468 if verbosity in ("detailed", "debug"):
469 sections.append(create_appendix_section(results))
471 return sections