#!/usr/bin/env python3 """ HTML Report Generator Generates a clean, professional HTML report from JSON report data with search and filter capabilities. """ import json from pathlib import Path from datetime import datetime from typing import Dict, List, Optional def format_date(date_str: Optional[str]) -> str: """Format date string for display.""" if not date_str: return "N/A" try: # Try parsing various formats if "/" in date_str: # MM/DD/YY or MM/DD/YYYY parts = date_str.split("/") if len(parts) == 3: if len(parts[2]) == 2: # Convert YY to YYYY parts[2] = "20" + parts[2] return "/".join(parts) # Already formatted or ISO format return date_str.split()[0] if " " in date_str else date_str except: return date_str def get_status_badge_class(status: str) -> str: """Get CSS class for status badge.""" status_lower = status.lower() if "complete" in status_lower: return "badge-success" elif "monitor" in status_lower: return "badge-warning" elif "incomplete" in status_lower: return "badge-danger" else: return "badge-secondary" def get_priority_badge_class(priority: Optional[str]) -> str: """Get CSS class for priority badge.""" if not priority: return "badge-secondary" priority_lower = priority.lower() if "very high" in priority_lower or "(1)" in priority_lower: return "badge-critical" elif "high" in priority_lower or "(2)" in priority_lower: return "badge-high" elif "medium" in priority_lower or "(3)" in priority_lower: return "badge-medium" elif "low" in priority_lower or "(4)" in priority_lower: return "badge-low" else: return "badge-secondary" def escape_js_string(s: str) -> str: """Escape a string for use in JavaScript double-quoted strings.""" return s.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n').replace('\r', '\\r') def generate_html_report(json_path: str, output_path: Optional[str] = None) -> str: """ Generate HTML report from JSON report file. Args: json_path: Path to JSON report file output_path: Optional output HTML file path (defaults to same location as JSON with .html extension) Returns: Path to generated HTML file """ # Load JSON report with open(json_path, 'r', encoding='utf-8') as f: report_data = json.load(f) # Determine output path if output_path is None: json_file = Path(json_path) output_path = json_file.parent / f"{json_file.stem}.html" else: output_path = Path(output_path) # Generate HTML html_content = generate_html_content(report_data) # Save HTML file output_path.parent.mkdir(parents=True, exist_ok=True) with open(output_path, 'w', encoding='utf-8') as f: f.write(html_content) return str(output_path) def generate_html_content(report_data: Dict) -> str: """Generate HTML content from report data.""" vendors = report_data.get('vendors', []) summary = report_data.get('summary', {}) report_generated_at = report_data.get('report_generated_at', datetime.now().isoformat()) # Format generation time try: gen_time = datetime.fromisoformat(report_generated_at.replace('Z', '+00:00')) gen_time_str = gen_time.strftime('%Y-%m-%d %H:%M:%S') except: gen_time_str = report_generated_at # Extract vendor names for filter vendor_names = sorted([v.get('vendor_name', '') for v in vendors if v.get('vendor_name')]) html = rf"""
Vendors
Total Items
Closed
Monitor
Open
Incomplete
Description: {description}
' if description else ''} {f'Date Identified: {format_date(date_identified)}
' if date_identified else ''} {f'Date Completed: {format_date(date_completed)}
' if date_completed else ''} {f'Status Updates: {status_updates}
' if status_updates else ''} {f'Image: {issue_image}
' if issue_image else ''}