- {evidence_items}
diff --git a/autorca_core/outputs/reports.py b/autorca_core/outputs/reports.py index 0a2902b..365b51d 100644 --- a/autorca_core/outputs/reports.py +++ b/autorca_core/outputs/reports.py @@ -3,10 +3,12 @@ """ import json -from typing import Dict, Any +from typing import Dict, Any, List from datetime import datetime from autorca_core.reasoning.loop import RCARunResult +from autorca_core.reasoning.rules import RootCauseCandidate +from autorca_core.model.graph import ServiceGraph from autorca_core.logging import get_logger logger = get_logger(__name__) @@ -154,47 +156,427 @@ def generate_json_report(result: RCARunResult, indent: int = 2) -> str: def generate_html_report(result: RCARunResult) -> str: """ - Generate an HTML-formatted RCA report. + Generate an interactive HTML-formatted RCA report with visualizations. - TODO: Implement HTML report generation with charts and visualizations. + Features: + - Service graph visualization (SVG) + - Interactive timeline + - Collapsible evidence sections + - Copy-to-clipboard functionality + - Self-contained (no external dependencies) Args: result: RCA run result Returns: - HTML-formatted report string + Interactive HTML-formatted report string """ - # For now, convert markdown to basic HTML - markdown = generate_markdown_report(result) - - html_parts = [ - "", - "", - "
", - " ", - "", - markdown, - "", - "", - "", + # Generate service graph SVG + graph_svg = _generate_service_graph_svg(result.service_graph, result.root_cause_candidates) + + # Generate timeline HTML + timeline_html = _generate_timeline_html(result.timeline) + + # Generate candidates HTML + candidates_html = _generate_candidates_html(result.root_cause_candidates) + + html = f""" + + + + +
No services detected
" + + # Find root cause services + root_cause_services = {c.service for c in candidates[:3]} + + # Simple force-directed layout + services = list(graph.services.keys()) + num_services = len(services) + + if num_services == 0: + return "No services to display
" + + # Calculate positions (simple circular layout) + import math + radius = max(200, num_services * 30) + positions = {} + for i, service in enumerate(services): + angle = 2 * math.pi * i / num_services + x = 400 + radius * math.cos(angle) + y = 300 + radius * math.sin(angle) + positions[service] = (x, y) + + # Build SVG + svg_parts = [ + f'') + return '\n'.join(svg_parts) + + +def _generate_timeline_html(timeline: List[Dict[str, Any]]) -> str: + """Generate HTML for incident timeline.""" + if not timeline: + return "No incidents detected
" + + html_parts = [] + for incident in timeline[:20]: # Limit to 20 + timestamp = incident['timestamp'][:19] + service = incident['service'] + inc_type = incident['type'] + description = incident['description'] + severity = incident['severity'] + + html_parts.append( + f'No root cause candidates identified
" + + html_parts = [] + for i, candidate in enumerate(candidates[:5], 1): + evidence_items = '\n'.join( + f'{candidate.explanation}
+ + +