car_web2 / analysis /report_generator.py
wesam0099's picture
Deploy CrashLens
8adf0de
"""
Report Generator
================
Generates PDF and HTML reports for accident analysis.
"""
import os
from datetime import datetime
from typing import Dict, List, Any
from pathlib import Path
from config import REPORTS_DIR, REPORT_CONFIG, VEHICLE_TYPES
def generate_report(
results: Dict[str, Any],
scenarios: List[Dict[str, Any]],
accident_info: Dict[str, Any],
vehicle_1: Dict[str, Any],
vehicle_2: Dict[str, Any],
format: str = 'pdf',
include_maps: bool = True,
include_charts: bool = True,
include_raw_data: bool = False
) -> str:
"""
Generate a comprehensive accident analysis report.
Args:
results: Analysis results dictionary
scenarios: List of generated scenarios
accident_info: Accident context information
vehicle_1: First vehicle data
vehicle_2: Second vehicle data
format: Output format ('pdf' or 'html')
include_maps: Whether to include map visualizations
include_charts: Whether to include charts
include_raw_data: Whether to include raw data tables
Returns:
Path to generated report file
"""
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
if format.lower() == 'html':
return generate_html_report(
results, scenarios, accident_info, vehicle_1, vehicle_2,
timestamp, include_maps, include_charts, include_raw_data
)
else:
return generate_pdf_report(
results, scenarios, accident_info, vehicle_1, vehicle_2,
timestamp, include_maps, include_charts, include_raw_data
)
def generate_html_report(
results: Dict[str, Any],
scenarios: List[Dict[str, Any]],
accident_info: Dict[str, Any],
vehicle_1: Dict[str, Any],
vehicle_2: Dict[str, Any],
timestamp: str,
include_maps: bool,
include_charts: bool,
include_raw_data: bool
) -> str:
"""Generate HTML report."""
# Get most likely scenario
most_likely = results.get('most_likely_scenario', {})
fault = results.get('preliminary_fault_assessment', {})
v1_type = VEHICLE_TYPES.get(vehicle_1.get('type', 'sedan'), {}).get('name', 'Sedan')
v2_type = VEHICLE_TYPES.get(vehicle_2.get('type', 'sedan'), {}).get('name', 'Sedan')
html_content = f"""
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Traffic Accident Analysis Report</title>
<style>
* {{
margin: 0;
padding: 0;
box-sizing: border-box;
}}
body {{
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
background: #f5f5f5;
}}
.container {{
max-width: 1000px;
margin: 0 auto;
background: white;
box-shadow: 0 0 20px rgba(0,0,0,0.1);
}}
.header {{
background: linear-gradient(135deg, #1e3a5f 0%, #2d5a87 100%);
color: white;
padding: 40px;
text-align: center;
}}
.header h1 {{
font-size: 2.5rem;
margin-bottom: 10px;
}}
.header .subtitle {{
opacity: 0.9;
font-size: 1.1rem;
}}
.section {{
padding: 30px 40px;
border-bottom: 1px solid #eee;
}}
.section h2 {{
color: #1e3a5f;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 2px solid #2d5a87;
}}
.summary-grid {{
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin: 20px 0;
}}
.summary-card {{
background: #f8f9fa;
padding: 20px;
border-radius: 10px;
text-align: center;
border-top: 4px solid #2d5a87;
}}
.summary-card .label {{
font-size: 0.9rem;
color: #666;
margin-bottom: 5px;
}}
.summary-card .value {{
font-size: 1.8rem;
font-weight: bold;
color: #1e3a5f;
}}
.summary-card .delta {{
font-size: 0.85rem;
margin-top: 5px;
}}
.delta.high {{ color: #28a745; }}
.delta.medium {{ color: #ffc107; }}
.delta.low {{ color: #dc3545; }}
.info-grid {{
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 30px;
}}
.info-box {{
background: #f8f9fa;
padding: 20px;
border-radius: 10px;
}}
.info-box h3 {{
color: #1e3a5f;
margin-bottom: 15px;
}}
.info-row {{
display: flex;
justify-content: space-between;
padding: 8px 0;
border-bottom: 1px solid #eee;
}}
.info-row:last-child {{
border-bottom: none;
}}
.vehicle-card {{
background: white;
border-radius: 10px;
padding: 20px;
margin: 15px 0;
}}
.vehicle-card.v1 {{
border-left: 4px solid #FF4B4B;
}}
.vehicle-card.v2 {{
border-left: 4px solid #4B7BFF;
}}
.scenario-card {{
background: #f8f9fa;
border-radius: 10px;
padding: 20px;
margin: 15px 0;
border-left: 4px solid #2d5a87;
}}
.scenario-header {{
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}}
.probability {{
font-size: 1.5rem;
font-weight: bold;
}}
.probability.high {{ color: #28a745; }}
.probability.medium {{ color: #ffc107; }}
.probability.low {{ color: #dc3545; }}
.progress-bar {{
background: #e9ecef;
border-radius: 5px;
height: 10px;
margin: 10px 0;
overflow: hidden;
}}
.progress-fill {{
background: #2d5a87;
height: 100%;
border-radius: 5px;
}}
.factors-list {{
list-style: none;
padding: 0;
}}
.factors-list li {{
padding: 5px 0;
padding-left: 20px;
position: relative;
}}
.factors-list li::before {{
content: "•";
color: #2d5a87;
position: absolute;
left: 0;
}}
.timeline {{
margin: 20px 0;
}}
.timeline-item {{
display: flex;
margin: 10px 0;
}}
.timeline-time {{
min-width: 80px;
padding: 8px 15px;
background: #ffc107;
color: white;
font-weight: bold;
text-align: center;
border-radius: 5px;
}}
.timeline-time.impact {{
background: #dc3545;
}}
.timeline-time.after {{
background: #28a745;
}}
.timeline-event {{
flex: 1;
padding: 8px 15px;
background: #f8f9fa;
margin-left: 10px;
border-radius: 5px;
}}
.fault-assessment {{
background: #fff3cd;
padding: 20px;
border-radius: 10px;
border-left: 4px solid #ffc107;
margin: 20px 0;
}}
.footer {{
background: #1e3a5f;
color: white;
padding: 30px 40px;
text-align: center;
}}
.footer p {{
opacity: 0.8;
font-size: 0.9rem;
}}
@media print {{
.container {{
box-shadow: none;
}}
.section {{
page-break-inside: avoid;
}}
}}
</style>
</head>
<body>
<div class="container">
<!-- Header -->
<div class="header">
<h1>🚗 Traffic Accident Analysis Report</h1>
<p class="subtitle">AI-Powered Analysis using Huawei MindSpore</p>
<p style="margin-top: 15px; opacity: 0.7;">Generated: {datetime.now().strftime("%B %d, %Y at %H:%M")}</p>
</div>
<!-- Executive Summary -->
<div class="section">
<h2>📊 Executive Summary</h2>
<div class="summary-grid">
<div class="summary-card">
<div class="label">Most Likely Scenario</div>
<div class="value">#{most_likely.get('id', 1)}</div>
<div class="delta high">{most_likely.get('probability', 0)*100:.1f}% probability</div>
</div>
<div class="summary-card">
<div class="label">Scenarios Generated</div>
<div class="value">{len(scenarios)}</div>
<div class="delta">AI-generated</div>
</div>
<div class="summary-card">
<div class="label">Collision Certainty</div>
<div class="value">{results.get('overall_collision_probability', 0)*100:.1f}%</div>
<div class="delta {'high' if results.get('overall_collision_probability', 0) > 0.7 else 'medium' if results.get('overall_collision_probability', 0) > 0.4 else 'low'}">
{'High' if results.get('overall_collision_probability', 0) > 0.7 else 'Medium' if results.get('overall_collision_probability', 0) > 0.4 else 'Low'}
</div>
</div>
<div class="summary-card">
<div class="label">Primary Factor</div>
<div class="value" style="font-size: 1.2rem;">{fault.get('primary_factor', 'N/A').replace('_', ' ').title()[:20]}</div>
<div class="delta">Vehicle {fault.get('likely_at_fault', '?')}</div>
</div>
</div>
</div>
<!-- Accident Details -->
<div class="section">
<h2>📍 Accident Details</h2>
<div class="info-grid">
<div class="info-box">
<h3>Location Information</h3>
<div class="info-row">
<span>Location:</span>
<strong>{accident_info.get('location', {}).get('name', 'Unknown')}</strong>
</div>
<div class="info-row">
<span>Coordinates:</span>
<strong>{accident_info.get('location', {}).get('latitude', 0):.4f}, {accident_info.get('location', {}).get('longitude', 0):.4f}</strong>
</div>
<div class="info-row">
<span>Road Type:</span>
<strong>{accident_info.get('road_type', 'Unknown').replace('_', ' ').title()}</strong>
</div>
</div>
<div class="info-box">
<h3>Conditions</h3>
<div class="info-row">
<span>Date/Time:</span>
<strong>{accident_info.get('datetime', 'Not specified')}</strong>
</div>
<div class="info-row">
<span>Weather:</span>
<strong>{accident_info.get('weather', 'Unknown').title()}</strong>
</div>
<div class="info-row">
<span>Road Condition:</span>
<strong>{accident_info.get('road_condition', 'Unknown').title()}</strong>
</div>
</div>
</div>
</div>
<!-- Vehicle Information -->
<div class="section">
<h2>🚙 Vehicle Information</h2>
<div class="info-grid">
<div class="vehicle-card v1">
<h3 style="color: #FF4B4B;">Vehicle 1 (Red)</h3>
<div class="info-row">
<span>Type:</span>
<strong>{v1_type}</strong>
</div>
<div class="info-row">
<span>Speed:</span>
<strong>{vehicle_1.get('speed', 0)} km/h</strong>
</div>
<div class="info-row">
<span>Direction:</span>
<strong>{vehicle_1.get('direction', 'Unknown').title()}</strong>
</div>
<div class="info-row">
<span>Action:</span>
<strong>{vehicle_1.get('action', 'Unknown').replace('_', ' ').title()}</strong>
</div>
<div class="info-row">
<span>Braking:</span>
<strong>{'Yes' if vehicle_1.get('braking', False) else 'No'}</strong>
</div>
<div class="info-row">
<span>Signaling:</span>
<strong>{'Yes' if vehicle_1.get('signaling', False) else 'No'}</strong>
</div>
</div>
<div class="vehicle-card v2">
<h3 style="color: #4B7BFF;">Vehicle 2 (Blue)</h3>
<div class="info-row">
<span>Type:</span>
<strong>{v2_type}</strong>
</div>
<div class="info-row">
<span>Speed:</span>
<strong>{vehicle_2.get('speed', 0)} km/h</strong>
</div>
<div class="info-row">
<span>Direction:</span>
<strong>{vehicle_2.get('direction', 'Unknown').title()}</strong>
</div>
<div class="info-row">
<span>Action:</span>
<strong>{vehicle_2.get('action', 'Unknown').replace('_', ' ').title()}</strong>
</div>
<div class="info-row">
<span>Braking:</span>
<strong>{'Yes' if vehicle_2.get('braking', False) else 'No'}</strong>
</div>
<div class="info-row">
<span>Signaling:</span>
<strong>{'Yes' if vehicle_2.get('signaling', False) else 'No'}</strong>
</div>
</div>
</div>
</div>
<!-- Generated Scenarios -->
<div class="section">
<h2>🎯 AI-Generated Scenarios</h2>
{''.join([f'''
<div class="scenario-card">
<div class="scenario-header">
<div>
<h3>Scenario {i+1}: {s['accident_type'].replace('_', ' ').title()}</h3>
</div>
<div class="probability {'high' if s['probability'] > 0.4 else 'medium' if s['probability'] > 0.2 else 'low'}">
{s['probability']*100:.1f}%
</div>
</div>
<p>{s['description']}</p>
<div style="margin-top: 15px;">
<strong>Analysis Metrics:</strong>
<div style="margin-top: 10px;">
<span>Collision Probability: {s['metrics']['collision_probability']*100:.1f}%</span>
<div class="progress-bar">
<div class="progress-fill" style="width: {s['metrics']['collision_probability']*100}%"></div>
</div>
</div>
<div>
<span>Path Overlap: {s['metrics']['path_overlap']*100:.1f}%</span>
<div class="progress-bar">
<div class="progress-fill" style="width: {s['metrics']['path_overlap']*100}%"></div>
</div>
</div>
<p style="margin-top: 10px;">Speed Differential: {s['metrics']['speed_differential']:.1f} km/h | Time to Collision: {s['metrics']['time_to_collision']:.2f}s</p>
</div>
<div style="margin-top: 15px;">
<strong>Contributing Factors:</strong>
<ul class="factors-list">
{''.join([f"<li>{f.replace('_', ' ').title()}</li>" for f in s['contributing_factors']])}
</ul>
</div>
</div>
''' for i, s in enumerate(scenarios)])}
</div>
<!-- Fault Assessment -->
<div class="section">
<h2>⚖️ Preliminary Fault Assessment</h2>
<div class="fault-assessment">
<h3>⚠️ Disclaimer</h3>
<p>This is a preliminary AI-generated assessment for reference purposes only. Final fault determination should be made by qualified traffic authorities based on comprehensive investigation.</p>
</div>
<div class="info-grid" style="margin-top: 20px;">
<div class="info-box">
<h3>Contribution Analysis</h3>
<div style="margin: 15px 0;">
<span style="color: #FF4B4B;">Vehicle 1: {fault.get('vehicle_1_contribution', 50):.1f}%</span>
<div class="progress-bar">
<div class="progress-fill" style="width: {fault.get('vehicle_1_contribution', 50)}%; background: #FF4B4B;"></div>
</div>
</div>
<div>
<span style="color: #4B7BFF;">Vehicle 2: {fault.get('vehicle_2_contribution', 50):.1f}%</span>
<div class="progress-bar">
<div class="progress-fill" style="width: {fault.get('vehicle_2_contribution', 50)}%; background: #4B7BFF;"></div>
</div>
</div>
</div>
<div class="info-box">
<h3>Assessment Summary</h3>
<div class="info-row">
<span>Higher Contribution:</span>
<strong>Vehicle {fault.get('likely_at_fault', '?')}</strong>
</div>
<div class="info-row">
<span>Primary Factor:</span>
<strong>{fault.get('primary_factor', 'Unknown').replace('_', ' ').title()}</strong>
</div>
<div class="info-row">
<span>Assessment Confidence:</span>
<strong>{fault.get('confidence', 0)*100:.1f}%</strong>
</div>
</div>
</div>
</div>
<!-- Timeline -->
<div class="section">
<h2>⏱️ Estimated Accident Timeline</h2>
<div class="timeline">
{''.join([f'''
<div class="timeline-item">
<div class="timeline-time {'impact' if e['time'] == 0 else 'after' if e['time'] > 0 else ''}">{e['time']:+.1f}s</div>
<div class="timeline-event">{e['event']}</div>
</div>
''' for e in results.get('timeline', [])])}
</div>
</div>
<!-- Footer -->
<div class="footer">
<p><strong>Traffic Accident Reconstruction System</strong></p>
<p>Huawei AI Innovation Challenge 2026</p>
<p style="margin-top: 10px;">Powered by Huawei MindSpore AI Framework</p>
<p style="margin-top: 15px; font-size: 0.8rem;">Report ID: {timestamp}</p>
</div>
</div>
</body>
</html>
"""
# Save the report
report_path = REPORTS_DIR / f"accident_report_{timestamp}.html"
with open(report_path, 'w', encoding='utf-8') as f:
f.write(html_content)
return str(report_path)
def generate_pdf_report(
results: Dict[str, Any],
scenarios: List[Dict[str, Any]],
accident_info: Dict[str, Any],
vehicle_1: Dict[str, Any],
vehicle_2: Dict[str, Any],
timestamp: str,
include_maps: bool,
include_charts: bool,
include_raw_data: bool
) -> str:
"""Generate PDF report using ReportLab."""
try:
from reportlab.lib import colors
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import inch, cm
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, PageBreak
from reportlab.lib.enums import TA_CENTER, TA_LEFT
except ImportError:
# Fall back to HTML if ReportLab not available
return generate_html_report(
results, scenarios, accident_info, vehicle_1, vehicle_2,
timestamp, include_maps, include_charts, include_raw_data
)
report_path = REPORTS_DIR / f"accident_report_{timestamp}.pdf"
doc = SimpleDocTemplate(
str(report_path),
pagesize=A4,
rightMargin=72,
leftMargin=72,
topMargin=72,
bottomMargin=72
)
styles = getSampleStyleSheet()
# Custom styles
title_style = ParagraphStyle(
'CustomTitle',
parent=styles['Heading1'],
fontSize=24,
spaceAfter=30,
alignment=TA_CENTER,
textColor=colors.HexColor('#1e3a5f')
)
heading_style = ParagraphStyle(
'CustomHeading',
parent=styles['Heading2'],
fontSize=16,
spaceBefore=20,
spaceAfter=10,
textColor=colors.HexColor('#2d5a87')
)
story = []
# Title
story.append(Paragraph("Traffic Accident Analysis Report", title_style))
story.append(Paragraph("AI-Powered Analysis using Huawei MindSpore", styles['Normal']))
story.append(Spacer(1, 30))
# Executive Summary
story.append(Paragraph("Executive Summary", heading_style))
most_likely = results.get('most_likely_scenario', {})
fault = results.get('preliminary_fault_assessment', {})
summary_data = [
['Most Likely Scenario', f"#{most_likely.get('id', 1)} ({most_likely.get('probability', 0)*100:.1f}%)"],
['Scenarios Generated', str(len(scenarios))],
['Collision Certainty', f"{results.get('overall_collision_probability', 0)*100:.1f}%"],
['Primary Factor', fault.get('primary_factor', 'N/A').replace('_', ' ').title()]
]
summary_table = Table(summary_data, colWidths=[3*inch, 3*inch])
summary_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (0, -1), colors.HexColor('#f8f9fa')),
('TEXTCOLOR', (0, 0), (-1, -1), colors.black),
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
('FONTNAME', (0, 0), (-1, -1), 'Helvetica'),
('FONTSIZE', (0, 0), (-1, -1), 10),
('BOTTOMPADDING', (0, 0), (-1, -1), 12),
('TOPPADDING', (0, 0), (-1, -1), 12),
('GRID', (0, 0), (-1, -1), 1, colors.HexColor('#dee2e6'))
]))
story.append(summary_table)
story.append(Spacer(1, 20))
# Location Details
story.append(Paragraph("Accident Details", heading_style))
location_data = [
['Location', accident_info.get('location', {}).get('name', 'Unknown')],
['Road Type', accident_info.get('road_type', 'Unknown').replace('_', ' ').title()],
['Weather', accident_info.get('weather', 'Unknown').title()],
['Road Condition', accident_info.get('road_condition', 'Unknown').title()]
]
location_table = Table(location_data, colWidths=[2*inch, 4*inch])
location_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (0, -1), colors.HexColor('#f8f9fa')),
('GRID', (0, 0), (-1, -1), 1, colors.HexColor('#dee2e6')),
('FONTNAME', (0, 0), (-1, -1), 'Helvetica'),
('FONTSIZE', (0, 0), (-1, -1), 10),
('BOTTOMPADDING', (0, 0), (-1, -1), 8),
('TOPPADDING', (0, 0), (-1, -1), 8),
]))
story.append(location_table)
story.append(Spacer(1, 20))
# Scenarios
story.append(Paragraph("Generated Scenarios", heading_style))
for i, scenario in enumerate(scenarios):
story.append(Paragraph(
f"<b>Scenario {i+1}: {scenario['accident_type'].replace('_', ' ').title()}</b> - {scenario['probability']*100:.1f}% probability",
styles['Normal']
))
story.append(Paragraph(scenario['description'], styles['Normal']))
story.append(Spacer(1, 10))
# Build PDF
doc.build(story)
return str(report_path)