wesam0099 commited on
Commit
f158aab
·
1 Parent(s): 1512569

Deploy REAL original app

Browse files
.gitattributes CHANGED
@@ -1,16 +1,5 @@
1
- # Git LFS Configuration for CrashLens
2
- # Large File Storage for model files and media
3
- # AI Model Files
4
  *.ckpt filter=lfs diff=lfs merge=lfs -text
5
  *.onnx filter=lfs diff=lfs merge=lfs -text
6
- *.pb filter=lfs diff=lfs merge=lfs -text
7
- *.h5 filter=lfs diff=lfs merge=lfs -text
8
- *.pth filter=lfs diff=lfs merge=lfs -text
9
- # Data Files
10
  *.npz filter=lfs diff=lfs merge=lfs -text
11
- *.npy filter=lfs diff=lfs merge=lfs -text
12
- # Video/Animation Files
13
  *.gif filter=lfs diff=lfs merge=lfs -text
14
- *.mp4 filter=lfs diff=lfs merge=lfs -text
15
- # Documentation
16
  *.pdf filter=lfs diff=lfs merge=lfs -text
 
 
 
 
1
  *.ckpt filter=lfs diff=lfs merge=lfs -text
2
  *.onnx filter=lfs diff=lfs merge=lfs -text
 
 
 
 
3
  *.npz filter=lfs diff=lfs merge=lfs -text
 
 
4
  *.gif filter=lfs diff=lfs merge=lfs -text
 
 
5
  *.pdf filter=lfs diff=lfs merge=lfs -text
Dockerfile CHANGED
@@ -2,32 +2,14 @@ FROM python:3.9-slim
2
 
3
  WORKDIR /app
4
 
5
- # Install system dependencies
6
- RUN apt-get update && apt-get install -y \
7
- build-essential \
8
- curl \
9
- && rm -rf /var/lib/apt/lists/*
10
 
11
- # Copy requirements
12
  COPY requirements.txt .
13
-
14
- # Install Python packages
15
  RUN pip install --no-cache-dir -r requirements.txt
16
 
17
- # Copy all application files
18
  COPY . .
19
-
20
- # Create output directories
21
  RUN mkdir -p output/reports logs
22
 
23
- # Expose port 7860 for Hugging Face Spaces
24
  EXPOSE 7860
25
 
26
- # Environment variables
27
- ENV STREAMLIT_SERVER_PORT=7860
28
- ENV STREAMLIT_SERVER_ADDRESS=0.0.0.0
29
- ENV STREAMLIT_SERVER_HEADLESS=true
30
- ENV STREAMLIT_BROWSER_GATHER_USAGE_STATS=false
31
-
32
- # Run the app
33
- CMD ["streamlit", "run", "app.py", "--server.port=7860", "--server.address=0.0.0.0"]
 
2
 
3
  WORKDIR /app
4
 
5
+ RUN apt-get update && apt-get install -y build-essential curl && rm -rf /var/lib/apt/lists/*
 
 
 
 
6
 
 
7
  COPY requirements.txt .
 
 
8
  RUN pip install --no-cache-dir -r requirements.txt
9
 
 
10
  COPY . .
 
 
11
  RUN mkdir -p output/reports logs
12
 
 
13
  EXPOSE 7860
14
 
15
+ CMD streamlit run app.py --server.port=7860 --server.address=0.0.0.0 --server.headless=true --server.fileWatcherType=none --browser.gatherUsageStats=false
 
 
 
 
 
 
 
README.md ADDED
@@ -0,0 +1,279 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🚗 Traffic Accident Reconstruction System
2
+
3
+ ## AI-Powered Analysis using Huawei MindSpore
4
+
5
+ > **Huawei AI Innovation Challenge 2026**
6
+ > AI Research Team
7
+
8
+ ---
9
+
10
+ ## 📋 Project Overview
11
+
12
+ This system is an intelligent traffic accident reconstruction tool that helps traffic authorities understand accidents in a clearer and fairer manner. It reconstructs accidents at their real geographic locations using 2D traffic simulation and AI-powered scenario generation.
13
+
14
+ ### Key Features
15
+
16
+ - **Real Map Integration**: Uses OpenStreetMap to display actual road layouts
17
+ - **AI Scenario Generation**: MindSpore-powered analysis generates multiple accident scenarios
18
+ - **Probability Analysis**: Each scenario includes probability scores and contributing factors
19
+ - **2D Traffic Simulation**: SUMO integration for realistic accident reconstruction
20
+ - **Comprehensive Reports**: PDF and HTML report generation
21
+
22
+ ---
23
+
24
+ ## 🛠️ Technology Stack
25
+
26
+ | Component | Technology |
27
+ |-----------|------------|
28
+ | **AI Framework** | Huawei MindSpore |
29
+ | **Web Interface** | Streamlit |
30
+ | **Traffic Simulation** | SUMO |
31
+ | **Map Data** | OpenStreetMap |
32
+ | **Data Processing** | NumPy, Pandas |
33
+ | **Visualization** | Plotly, Folium |
34
+ | **Report Generation** | ReportLab, Jinja2 |
35
+
36
+ ---
37
+
38
+ ## 📁 Project Structure
39
+
40
+ ```
41
+ traffic_accident_analyzer/
42
+ ├── app.py # Main Streamlit application
43
+ ├── config.py # Configuration settings
44
+ ├── requirements.txt # Python dependencies
45
+ ├── setup.sh # Setup script
46
+ ├── README.md # This file
47
+
48
+ ├── ui/ # User interface components
49
+ │ ├── __init__.py
50
+ │ ├── components.py # Reusable UI components
51
+ │ ├── map_viewer.py # Map display and path drawing
52
+ │ ├── vehicle_input.py # Vehicle data input forms
53
+ │ └── results_display.py # Results visualization
54
+
55
+ ├── analysis/ # Analysis modules
56
+ │ ├── __init__.py
57
+ │ ├── scenario_analyzer.py # AI scenario generation
58
+ │ └── report_generator.py # Report generation
59
+
60
+ ├── models/ # MindSpore models
61
+ │ ├── __init__.py
62
+ │ ├── mindspore_model.py # MindSpore neural network
63
+ │ └── trained/ # Saved model weights
64
+
65
+ ├── simulation/ # SUMO simulation
66
+ │ ├── __init__.py
67
+ │ ├── sumo_interface.py # SUMO integration
68
+ │ ├── networks/ # SUMO network files
69
+ │ └── output/ # Simulation outputs
70
+
71
+ ├── data/ # Data files
72
+ │ ├── raw/ # Raw data
73
+ │ ├── processed/ # Processed data
74
+ │ └── osm/ # OpenStreetMap data
75
+
76
+ ├── output/ # Output files
77
+ │ ├── reports/ # Generated reports
78
+ │ └── visualizations/ # Generated visualizations
79
+
80
+ └── logs/ # Application logs
81
+ ```
82
+
83
+ ---
84
+
85
+ ## 🚀 Quick Start
86
+
87
+ ### Prerequisites
88
+
89
+ - Python 3.8 or higher
90
+ - SUMO (for traffic simulation)
91
+ - Git
92
+
93
+ ### Installation
94
+
95
+ 1. **Clone or download the project**
96
+ ```bash
97
+ cd traffic_accident_analyzer
98
+ ```
99
+
100
+ 2. **Run the setup script**
101
+ ```bash
102
+ chmod +x setup.sh
103
+ ./setup.sh
104
+ ```
105
+
106
+ 3. **Or install manually**
107
+ ```bash
108
+ python3 -m venv venv
109
+ source venv/bin/activate # On Windows: venv\Scripts\activate
110
+ pip install -r requirements.txt
111
+ ```
112
+
113
+ 4. **Run the application**
114
+ ```bash
115
+ streamlit run app.py
116
+ ```
117
+
118
+ 5. **Open in browser**
119
+ - Navigate to `http://localhost:8501`
120
+
121
+ ---
122
+
123
+ ## 📖 How to Use
124
+
125
+ ### Step 1: Select Accident Location
126
+ - Choose a preset location or enter custom coordinates
127
+ - The system displays the real road layout from OpenStreetMap
128
+
129
+ ### Step 2: Enter Vehicle 1 Information
130
+ - Select vehicle type, speed, and direction
131
+ - Draw the vehicle's path on the map
132
+ - Add driver's description
133
+
134
+ ### Step 3: Enter Vehicle 2 Information
135
+ - Repeat for the second vehicle
136
+ - Both paths are displayed together
137
+
138
+ ### Step 4: Run AI Analysis
139
+ - Click "Run AI Analysis" to generate scenarios
140
+ - MindSpore AI processes the data
141
+
142
+ ### Step 5: View Results
143
+ - See multiple generated scenarios with probabilities
144
+ - View detailed metrics and contributing factors
145
+ - Generate PDF or HTML reports
146
+
147
+ ---
148
+
149
+ ## 🎯 System Workflow
150
+
151
+ ```
152
+ ┌─────────────────┐
153
+ │ User Input │
154
+ │ (Location, │
155
+ │ Vehicles) │
156
+ └────────┬────────┘
157
+
158
+
159
+ ┌─────────────────┐
160
+ │ OpenStreetMap │
161
+ │ (Road Layout) │
162
+ └────────┬────────┘
163
+
164
+
165
+ ┌─────────────────┐
166
+ │ Feature │
167
+ │ Extraction │
168
+ └────────┬────────┘
169
+
170
+
171
+ ┌─────────────────┐
172
+ │ MindSpore AI │
173
+ │ (Scenario │
174
+ │ Generation) │
175
+ └────────┬────────┘
176
+
177
+
178
+ ┌─────────────────┐
179
+ │ SUMO │
180
+ │ (Simulation) │
181
+ └────────┬────────┘
182
+
183
+
184
+ ┌─────────────────┐
185
+ │ Analysis & │
186
+ │ Report │
187
+ └─────────────────┘
188
+ ```
189
+
190
+ ---
191
+
192
+ ## 🧠 AI Model Details
193
+
194
+ ### Input Features (24 features)
195
+ - Vehicle speeds and dimensions
196
+ - Travel directions and angles
197
+ - Weather and road conditions
198
+ - Path overlap metrics
199
+ - Action risk factors
200
+
201
+ ### Output
202
+ - 5 most probable accident scenarios
203
+ - Probability scores for each
204
+ - Contributing factors
205
+ - Collision point estimation
206
+
207
+ ### Model Architecture
208
+ - Multi-layer neural network
209
+ - Built with MindSpore framework
210
+ - Trained on synthetic accident data
211
+
212
+ ---
213
+
214
+ ## 📊 Analysis Metrics
215
+
216
+ | Metric | Weight | Description |
217
+ |--------|--------|-------------|
218
+ | Collision Probability | 35% | Likelihood based on trajectories |
219
+ | Path Overlap | 25% | Degree of path intersection |
220
+ | Speed Differential | 20% | Speed difference at collision |
221
+ | Timing Analysis | 20% | Time gap at conflict point |
222
+
223
+ ---
224
+
225
+ ## 🌍 Case Study Location
226
+
227
+ **دوار السيف - Seef District Roundabout, Bahrain**
228
+
229
+ - **City:** Manama, Bahrain
230
+ - **Latitude:** 26.2397
231
+ - **Longitude:** 50.5369
232
+ - **Description:** Major roundabout in the Seef commercial district, near City Centre Bahrain mall
233
+
234
+ ---
235
+
236
+ ## 📄 Report Contents
237
+
238
+ - Executive Summary
239
+ - Accident Details
240
+ - Vehicle Information
241
+ - AI-Generated Scenarios
242
+ - Probability Analysis
243
+ - Contributing Factors
244
+ - Preliminary Fault Assessment
245
+ - Timeline Reconstruction
246
+ - Recommendations
247
+
248
+ ---
249
+
250
+ ## ⚠️ Disclaimer
251
+
252
+ This system provides AI-generated analysis for reference purposes only. Final accident determination should be made by qualified traffic authorities based on comprehensive investigation.
253
+
254
+ ---
255
+
256
+ ## 🏆 Competition Information
257
+
258
+ **Huawei AI Innovation Challenge 2026**
259
+
260
+ - **Track**: Innovation Track
261
+ - **Theme**: MindSpore AI Applications
262
+ - **Institution**: AI Research Team
263
+ - **Country**: Saudi Arabia
264
+
265
+ ---
266
+
267
+ ## 📧 Contact
268
+
269
+ For questions or support regarding this project, please contact the project team.
270
+
271
+ ---
272
+
273
+ ## 📜 License
274
+
275
+ This project is developed for the Huawei AI Innovation Challenge 2026.
276
+
277
+ ---
278
+
279
+ **Built with ❤️ using Huawei MindSpore**
data/__init__.py CHANGED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Data Package
3
+ =============
4
+ Data handling, schema definitions, and dataset generation for
5
+ the Traffic Accident Reconstruction System.
6
+ """
7
+
8
+ from .schema import (
9
+ # Enums
10
+ VehicleType,
11
+ Direction,
12
+ VehicleAction,
13
+ WeatherCondition,
14
+ RoadCondition,
15
+ AccidentType,
16
+ Severity,
17
+ RoadType,
18
+
19
+ # Data classes
20
+ Location,
21
+ Conditions,
22
+ VehicleData,
23
+ AccidentDetails,
24
+ ScenarioMetrics,
25
+ Scenario,
26
+ FaultAssessment,
27
+ TimelineEvent,
28
+ AccidentRecord,
29
+ AnalysisResult,
30
+
31
+ # Feature schema
32
+ FEATURE_SCHEMA,
33
+
34
+ # Validation functions
35
+ validate_speed,
36
+ validate_coordinates,
37
+ validate_path,
38
+ validate_probability
39
+ )
40
+
41
+ from .synthetic_dataset_generator import (
42
+ generate_dataset,
43
+ generate_single_accident,
44
+ generate_training_features,
45
+ save_dataset,
46
+ print_dataset_statistics
47
+ )
48
+
49
+ __all__ = [
50
+ # Enums
51
+ 'VehicleType',
52
+ 'Direction',
53
+ 'VehicleAction',
54
+ 'WeatherCondition',
55
+ 'RoadCondition',
56
+ 'AccidentType',
57
+ 'Severity',
58
+ 'RoadType',
59
+
60
+ # Data classes
61
+ 'Location',
62
+ 'Conditions',
63
+ 'VehicleData',
64
+ 'AccidentDetails',
65
+ 'ScenarioMetrics',
66
+ 'Scenario',
67
+ 'FaultAssessment',
68
+ 'TimelineEvent',
69
+ 'AccidentRecord',
70
+ 'AnalysisResult',
71
+
72
+ # Feature schema
73
+ 'FEATURE_SCHEMA',
74
+
75
+ # Validation
76
+ 'validate_speed',
77
+ 'validate_coordinates',
78
+ 'validate_path',
79
+ 'validate_probability',
80
+
81
+ # Dataset generation
82
+ 'generate_dataset',
83
+ 'generate_single_accident',
84
+ 'generate_training_features',
85
+ 'save_dataset',
86
+ 'print_dataset_statistics'
87
+ ]
data/accident_schema.json ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "accident_id": "string",
3
+ "timestamp": "datetime",
4
+ "location": {
5
+ "name": "string",
6
+ "latitude": "float",
7
+ "longitude": "float",
8
+ "road_type": "string"
9
+ },
10
+ "conditions": {
11
+ "weather": "string",
12
+ "road_condition": "string",
13
+ "visibility": "float",
14
+ "lighting": "string"
15
+ },
16
+ "vehicle_1": {
17
+ "type": "string",
18
+ "speed_kmh": "float",
19
+ "direction": "string",
20
+ "direction_angle": "float",
21
+ "action": "string",
22
+ "braking": "boolean",
23
+ "signaling": "boolean",
24
+ "path": "list[tuple]"
25
+ },
26
+ "vehicle_2": {
27
+ "type": "string",
28
+ "speed_kmh": "float",
29
+ "direction": "string",
30
+ "direction_angle": "float",
31
+ "action": "string",
32
+ "braking": "boolean",
33
+ "signaling": "boolean",
34
+ "path": "list[tuple]"
35
+ },
36
+ "accident_details": {
37
+ "type": "string",
38
+ "severity": "string",
39
+ "collision_angle": "float",
40
+ "collision_point": "tuple",
41
+ "contributing_factors": "list[string]",
42
+ "fault_vehicle": "int"
43
+ },
44
+ "outcomes": {
45
+ "scenario_probability": "float",
46
+ "damage_estimate": "string",
47
+ "injuries": "boolean"
48
+ }
49
+ }
data/osm_extractor.py ADDED
@@ -0,0 +1,409 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ OpenStreetMap Data Extractor
3
+ ============================
4
+ Downloads and processes real road network data from OpenStreetMap
5
+ for use in accident reconstruction simulation.
6
+ """
7
+
8
+ import os
9
+ import json
10
+ from pathlib import Path
11
+ from typing import Dict, List, Tuple, Optional
12
+
13
+ try:
14
+ import osmnx as ox
15
+ OSMNX_AVAILABLE = True
16
+ except ImportError:
17
+ OSMNX_AVAILABLE = False
18
+ print("Warning: osmnx not installed. Install with: pip install osmnx")
19
+
20
+ try:
21
+ import folium
22
+ FOLIUM_AVAILABLE = True
23
+ except ImportError:
24
+ FOLIUM_AVAILABLE = False
25
+
26
+ import sys
27
+ sys.path.insert(0, str(Path(__file__).parent.parent))
28
+
29
+ from config import CASE_STUDY_LOCATION, ALTERNATIVE_LOCATIONS, OSM_DATA_DIR
30
+
31
+
32
+ def download_road_network(
33
+ latitude: float,
34
+ longitude: float,
35
+ radius: int = 200,
36
+ network_type: str = "drive",
37
+ save_path: Optional[str] = None
38
+ ) -> Dict:
39
+ """
40
+ Download road network from OpenStreetMap.
41
+
42
+ Args:
43
+ latitude: Center latitude
44
+ longitude: Center longitude
45
+ radius: Radius in meters
46
+ network_type: Type of network ('drive', 'walk', 'bike', 'all')
47
+ save_path: Optional path to save the network
48
+
49
+ Returns:
50
+ Dictionary containing network data
51
+ """
52
+ if not OSMNX_AVAILABLE:
53
+ print("osmnx not available. Using fallback data.")
54
+ return create_fallback_network(latitude, longitude, radius)
55
+
56
+ try:
57
+ # Configure osmnx
58
+ ox.settings.use_cache = True
59
+ ox.settings.log_console = True
60
+
61
+ # Download the road network
62
+ print(f"Downloading road network for ({latitude}, {longitude})...")
63
+ G = ox.graph_from_point(
64
+ (latitude, longitude),
65
+ dist=radius,
66
+ network_type=network_type,
67
+ simplify=True
68
+ )
69
+
70
+ # Get nodes and edges
71
+ nodes, edges = ox.graph_to_gdfs(G)
72
+
73
+ # Convert to dictionary format
74
+ network_data = {
75
+ "center": {"lat": latitude, "lng": longitude},
76
+ "radius": radius,
77
+ "nodes": [],
78
+ "edges": [],
79
+ "bounds": {
80
+ "north": nodes.geometry.y.max(),
81
+ "south": nodes.geometry.y.min(),
82
+ "east": nodes.geometry.x.max(),
83
+ "west": nodes.geometry.x.min()
84
+ }
85
+ }
86
+
87
+ # Process nodes
88
+ for idx, row in nodes.iterrows():
89
+ network_data["nodes"].append({
90
+ "id": str(idx),
91
+ "lat": row.geometry.y,
92
+ "lng": row.geometry.x
93
+ })
94
+
95
+ # Process edges
96
+ for idx, row in edges.iterrows():
97
+ edge_data = {
98
+ "from": str(idx[0]),
99
+ "to": str(idx[1]),
100
+ "length": row.get("length", 0),
101
+ "name": row.get("name", "Unknown"),
102
+ "highway": row.get("highway", "unclassified"),
103
+ "lanes": row.get("lanes", 1),
104
+ "maxspeed": row.get("maxspeed", "50")
105
+ }
106
+
107
+ # Get geometry if available
108
+ if hasattr(row.geometry, 'coords'):
109
+ edge_data["geometry"] = list(row.geometry.coords)
110
+
111
+ network_data["edges"].append(edge_data)
112
+
113
+ # Save if path provided
114
+ if save_path:
115
+ save_path = Path(save_path)
116
+ save_path.parent.mkdir(parents=True, exist_ok=True)
117
+
118
+ with open(save_path, 'w') as f:
119
+ json.dump(network_data, f, indent=2)
120
+
121
+ # Also save as GraphML for SUMO conversion
122
+ graphml_path = save_path.with_suffix('.graphml')
123
+ ox.save_graphml(G, graphml_path)
124
+
125
+ print(f"Network saved to {save_path}")
126
+ print(f"GraphML saved to {graphml_path}")
127
+
128
+ return network_data
129
+
130
+ except Exception as e:
131
+ print(f"Error downloading network: {e}")
132
+ return create_fallback_network(latitude, longitude, radius)
133
+
134
+
135
+ def create_fallback_network(
136
+ latitude: float,
137
+ longitude: float,
138
+ radius: int = 200
139
+ ) -> Dict:
140
+ """
141
+ Create a fallback network when OSM download fails.
142
+ Creates a simple roundabout structure.
143
+ """
144
+ import math
145
+
146
+ # Create a simple roundabout with 4 approaches
147
+ network_data = {
148
+ "center": {"lat": latitude, "lng": longitude},
149
+ "radius": radius,
150
+ "nodes": [],
151
+ "edges": [],
152
+ "bounds": {
153
+ "north": latitude + 0.002,
154
+ "south": latitude - 0.002,
155
+ "east": longitude + 0.002,
156
+ "west": longitude - 0.002
157
+ }
158
+ }
159
+
160
+ # Create roundabout nodes (8 points in a circle)
161
+ roundabout_radius = 0.0003 # Approximately 30 meters
162
+ num_points = 8
163
+
164
+ for i in range(num_points):
165
+ angle = (2 * math.pi * i) / num_points
166
+ lat = latitude + roundabout_radius * math.cos(angle)
167
+ lng = longitude + roundabout_radius * math.sin(angle)
168
+
169
+ network_data["nodes"].append({
170
+ "id": f"r{i}",
171
+ "lat": lat,
172
+ "lng": lng,
173
+ "type": "roundabout"
174
+ })
175
+
176
+ # Create approach nodes (4 directions)
177
+ approach_distance = 0.001 # Approximately 100 meters
178
+ approaches = [
179
+ ("north", latitude + approach_distance, longitude),
180
+ ("south", latitude - approach_distance, longitude),
181
+ ("east", latitude, longitude + approach_distance),
182
+ ("west", latitude, longitude - approach_distance)
183
+ ]
184
+
185
+ for name, lat, lng in approaches:
186
+ network_data["nodes"].append({
187
+ "id": f"a_{name}",
188
+ "lat": lat,
189
+ "lng": lng,
190
+ "type": "approach"
191
+ })
192
+
193
+ # Create roundabout edges (circular)
194
+ for i in range(num_points):
195
+ next_i = (i + 1) % num_points
196
+ network_data["edges"].append({
197
+ "from": f"r{i}",
198
+ "to": f"r{next_i}",
199
+ "length": 20,
200
+ "name": "Roundabout",
201
+ "highway": "primary",
202
+ "lanes": 2
203
+ })
204
+
205
+ # Connect approaches to roundabout
206
+ approach_connections = [
207
+ ("a_north", "r0", "r6"),
208
+ ("a_east", "r2", "r0"),
209
+ ("a_south", "r4", "r2"),
210
+ ("a_west", "r6", "r4")
211
+ ]
212
+
213
+ for approach, entry, exit in approach_connections:
214
+ # Entry edge
215
+ network_data["edges"].append({
216
+ "from": approach,
217
+ "to": entry,
218
+ "length": 80,
219
+ "name": "Approach Road",
220
+ "highway": "primary",
221
+ "lanes": 2
222
+ })
223
+ # Exit edge
224
+ network_data["edges"].append({
225
+ "from": exit,
226
+ "to": approach,
227
+ "length": 80,
228
+ "name": "Exit Road",
229
+ "highway": "primary",
230
+ "lanes": 2
231
+ })
232
+
233
+ return network_data
234
+
235
+
236
+ def extract_intersection_data(
237
+ latitude: float,
238
+ longitude: float,
239
+ radius: int = 100
240
+ ) -> Dict:
241
+ """
242
+ Extract intersection-specific data from OSM.
243
+ """
244
+ if not OSMNX_AVAILABLE:
245
+ return create_fallback_intersection(latitude, longitude)
246
+
247
+ try:
248
+ # Get features around the point
249
+ tags = {"highway": True}
250
+ gdf = ox.features_from_point((latitude, longitude), tags, dist=radius)
251
+
252
+ intersection_data = {
253
+ "center": {"lat": latitude, "lng": longitude},
254
+ "features": []
255
+ }
256
+
257
+ for idx, row in gdf.iterrows():
258
+ feature = {
259
+ "type": row.get("highway", "unknown"),
260
+ "name": row.get("name", "Unknown")
261
+ }
262
+ if hasattr(row.geometry, 'centroid'):
263
+ feature["lat"] = row.geometry.centroid.y
264
+ feature["lng"] = row.geometry.centroid.x
265
+
266
+ intersection_data["features"].append(feature)
267
+
268
+ return intersection_data
269
+
270
+ except Exception as e:
271
+ print(f"Error extracting intersection: {e}")
272
+ return create_fallback_intersection(latitude, longitude)
273
+
274
+
275
+ def create_fallback_intersection(latitude: float, longitude: float) -> Dict:
276
+ """Create fallback intersection data."""
277
+ return {
278
+ "center": {"lat": latitude, "lng": longitude},
279
+ "type": "roundabout",
280
+ "approaches": 4,
281
+ "lanes": 2,
282
+ "features": [
283
+ {"type": "primary", "name": "Main Road North-South"},
284
+ {"type": "primary", "name": "Main Road East-West"}
285
+ ]
286
+ }
287
+
288
+
289
+ def create_location_map(
290
+ location: Dict,
291
+ network_data: Optional[Dict] = None,
292
+ save_path: Optional[str] = None
293
+ ) -> 'folium.Map':
294
+ """
295
+ Create an interactive Folium map for the location.
296
+ """
297
+ if not FOLIUM_AVAILABLE:
298
+ print("Folium not available")
299
+ return None
300
+
301
+ # Create base map
302
+ m = folium.Map(
303
+ location=[location["latitude"], location["longitude"]],
304
+ zoom_start=17,
305
+ tiles="OpenStreetMap"
306
+ )
307
+
308
+ # Add center marker
309
+ folium.Marker(
310
+ location=[location["latitude"], location["longitude"]],
311
+ popup=location.get("name", "Location"),
312
+ icon=folium.Icon(color="red", icon="info-sign")
313
+ ).add_to(m)
314
+
315
+ # Add radius circle
316
+ folium.Circle(
317
+ location=[location["latitude"], location["longitude"]],
318
+ radius=location.get("radius_meters", 200),
319
+ color="blue",
320
+ fill=True,
321
+ fill_opacity=0.1
322
+ ).add_to(m)
323
+
324
+ # Add network data if available
325
+ if network_data:
326
+ # Add nodes
327
+ for node in network_data.get("nodes", []):
328
+ folium.CircleMarker(
329
+ location=[node["lat"], node["lng"]],
330
+ radius=3,
331
+ color="green",
332
+ fill=True
333
+ ).add_to(m)
334
+
335
+ # Add edges
336
+ for edge in network_data.get("edges", []):
337
+ if "geometry" in edge:
338
+ # Use actual geometry
339
+ coords = [(c[1], c[0]) for c in edge["geometry"]]
340
+ folium.PolyLine(
341
+ locations=coords,
342
+ color="gray",
343
+ weight=2
344
+ ).add_to(m)
345
+
346
+ # Save if path provided
347
+ if save_path:
348
+ m.save(save_path)
349
+ print(f"Map saved to {save_path}")
350
+
351
+ return m
352
+
353
+
354
+ def download_all_locations():
355
+ """Download network data for all configured locations."""
356
+
357
+ # Create output directory
358
+ OSM_DATA_DIR.mkdir(parents=True, exist_ok=True)
359
+
360
+ # Download primary location
361
+ print("\n" + "="*50)
362
+ print("Downloading primary location...")
363
+ print("="*50)
364
+
365
+ primary_data = download_road_network(
366
+ latitude=CASE_STUDY_LOCATION["latitude"],
367
+ longitude=CASE_STUDY_LOCATION["longitude"],
368
+ radius=CASE_STUDY_LOCATION["radius_meters"],
369
+ save_path=OSM_DATA_DIR / "primary_location.json"
370
+ )
371
+
372
+ # Create map for primary location
373
+ create_location_map(
374
+ CASE_STUDY_LOCATION,
375
+ primary_data,
376
+ save_path=str(OSM_DATA_DIR / "primary_location_map.html")
377
+ )
378
+
379
+ # Download alternative locations
380
+ for loc_key, loc_data in ALTERNATIVE_LOCATIONS.items():
381
+ print(f"\n" + "="*50)
382
+ print(f"Downloading {loc_data['name']}...")
383
+ print("="*50)
384
+
385
+ network_data = download_road_network(
386
+ latitude=loc_data["latitude"],
387
+ longitude=loc_data["longitude"],
388
+ radius=loc_data["radius_meters"],
389
+ save_path=OSM_DATA_DIR / f"{loc_key}.json"
390
+ )
391
+
392
+ create_location_map(
393
+ {
394
+ "latitude": loc_data["latitude"],
395
+ "longitude": loc_data["longitude"],
396
+ "radius_meters": loc_data["radius_meters"],
397
+ "name": loc_data["name"]
398
+ },
399
+ network_data,
400
+ save_path=str(OSM_DATA_DIR / f"{loc_key}_map.html")
401
+ )
402
+
403
+ print("\n" + "="*50)
404
+ print("All locations downloaded successfully!")
405
+ print("="*50)
406
+
407
+
408
+ if __name__ == "__main__":
409
+ download_all_locations()
data/processed/enhanced_synthetic_accidents_20260104_094620.csv ADDED
The diff for this file is too large to render. See raw diff
 
data/processed/synthetic_accidents.json ADDED
The diff for this file is too large to render. See raw diff
 
data/processed/synthetic_accidents_features.npz ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:27efbff25a33aa63bf871e238262e8382c494a77ee77f6ae0ad0db3bb6c42b32
3
+ size 48490
data/processed/synthetic_accidents_training.json ADDED
The diff for this file is too large to render. See raw diff
 
data/processed/synthetic_accidents_training_features.npz ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:19487c308417be0a125f6f5e57683ddd34bac7056776d34cfbe86f450246dca7
3
+ size 1280490
data/schema.py ADDED
@@ -0,0 +1,416 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Data Schema Definitions
3
+ =======================
4
+ Defines the structure of data used throughout the Traffic Accident
5
+ Reconstruction System.
6
+ """
7
+
8
+ from dataclasses import dataclass, field
9
+ from typing import List, Tuple, Optional, Dict, Any
10
+ from enum import Enum
11
+ from datetime import datetime
12
+
13
+
14
+ # ============================================================
15
+ # ENUMERATIONS
16
+ # ============================================================
17
+
18
+ class VehicleType(Enum):
19
+ """Types of vehicles."""
20
+ SEDAN = "sedan"
21
+ SUV = "suv"
22
+ TRUCK = "truck"
23
+ MOTORCYCLE = "motorcycle"
24
+ BUS = "bus"
25
+
26
+
27
+ class Direction(Enum):
28
+ """Cardinal and intercardinal directions."""
29
+ NORTH = "north"
30
+ NORTHEAST = "northeast"
31
+ EAST = "east"
32
+ SOUTHEAST = "southeast"
33
+ SOUTH = "south"
34
+ SOUTHWEST = "southwest"
35
+ WEST = "west"
36
+ NORTHWEST = "northwest"
37
+
38
+
39
+ class VehicleAction(Enum):
40
+ """Possible vehicle actions."""
41
+ GOING_STRAIGHT = "going_straight"
42
+ TURNING_LEFT = "turning_left"
43
+ TURNING_RIGHT = "turning_right"
44
+ ENTERING_ROUNDABOUT = "entering_roundabout"
45
+ EXITING_ROUNDABOUT = "exiting_roundabout"
46
+ CHANGING_LANE_LEFT = "changing_lane_left"
47
+ CHANGING_LANE_RIGHT = "changing_lane_right"
48
+ SLOWING_DOWN = "slowing_down"
49
+ ACCELERATING = "accelerating"
50
+ STOPPED = "stopped"
51
+
52
+
53
+ class WeatherCondition(Enum):
54
+ """Weather conditions."""
55
+ CLEAR = "clear"
56
+ CLOUDY = "cloudy"
57
+ RAINY = "rainy"
58
+ FOGGY = "foggy"
59
+ SANDSTORM = "sandstorm"
60
+
61
+
62
+ class RoadCondition(Enum):
63
+ """Road surface conditions."""
64
+ DRY = "dry"
65
+ WET = "wet"
66
+ SANDY = "sandy"
67
+ OILY = "oily"
68
+
69
+
70
+ class AccidentType(Enum):
71
+ """Types of traffic accidents."""
72
+ REAR_END_COLLISION = "rear_end_collision"
73
+ SIDE_IMPACT = "side_impact"
74
+ HEAD_ON_COLLISION = "head_on_collision"
75
+ SIDESWIPE = "sideswipe"
76
+ ROUNDABOUT_ENTRY_COLLISION = "roundabout_entry_collision"
77
+ LANE_CHANGE_COLLISION = "lane_change_collision"
78
+ INTERSECTION_COLLISION = "intersection_collision"
79
+
80
+
81
+ class Severity(Enum):
82
+ """Accident severity levels."""
83
+ MINOR = "minor"
84
+ MODERATE = "moderate"
85
+ SEVERE = "severe"
86
+
87
+
88
+ class RoadType(Enum):
89
+ """Types of roads."""
90
+ ROUNDABOUT = "roundabout"
91
+ INTERSECTION = "intersection"
92
+ HIGHWAY = "highway"
93
+ URBAN_ROAD = "urban_road"
94
+
95
+
96
+ # ============================================================
97
+ # DATA CLASSES
98
+ # ============================================================
99
+
100
+ @dataclass
101
+ class Location:
102
+ """Geographic location data."""
103
+ name: str
104
+ name_arabic: str = ""
105
+ latitude: float = 0.0
106
+ longitude: float = 0.0
107
+ radius_meters: int = 150
108
+ city: str = ""
109
+ country: str = ""
110
+ road_type: RoadType = RoadType.ROUNDABOUT
111
+
112
+ def to_dict(self) -> Dict[str, Any]:
113
+ return {
114
+ "name": self.name,
115
+ "name_arabic": self.name_arabic,
116
+ "latitude": self.latitude,
117
+ "longitude": self.longitude,
118
+ "radius_meters": self.radius_meters,
119
+ "city": self.city,
120
+ "country": self.country,
121
+ "road_type": self.road_type.value
122
+ }
123
+
124
+
125
+ @dataclass
126
+ class Conditions:
127
+ """Environmental conditions at time of accident."""
128
+ weather: WeatherCondition = WeatherCondition.CLEAR
129
+ road_condition: RoadCondition = RoadCondition.DRY
130
+ visibility: float = 1.0 # 0.0 to 1.0
131
+ lighting: str = "daylight" # daylight, dusk, night, artificial
132
+
133
+ def to_dict(self) -> Dict[str, Any]:
134
+ return {
135
+ "weather": self.weather.value,
136
+ "road_condition": self.road_condition.value,
137
+ "visibility": self.visibility,
138
+ "lighting": self.lighting
139
+ }
140
+
141
+
142
+ @dataclass
143
+ class VehicleData:
144
+ """Data for a single vehicle."""
145
+ vehicle_type: VehicleType = VehicleType.SEDAN
146
+ speed_kmh: float = 50.0
147
+ direction: Direction = Direction.NORTH
148
+ action: VehicleAction = VehicleAction.GOING_STRAIGHT
149
+ braking: bool = False
150
+ signaling: bool = False
151
+ lights_on: bool = True
152
+ horn_used: bool = False
153
+ path: List[Tuple[float, float]] = field(default_factory=list)
154
+ description: str = ""
155
+
156
+ @property
157
+ def direction_angle(self) -> float:
158
+ """Convert direction to angle in degrees."""
159
+ angles = {
160
+ Direction.NORTH: 0,
161
+ Direction.NORTHEAST: 45,
162
+ Direction.EAST: 90,
163
+ Direction.SOUTHEAST: 135,
164
+ Direction.SOUTH: 180,
165
+ Direction.SOUTHWEST: 225,
166
+ Direction.WEST: 270,
167
+ Direction.NORTHWEST: 315
168
+ }
169
+ return angles.get(self.direction, 0)
170
+
171
+ def to_dict(self) -> Dict[str, Any]:
172
+ return {
173
+ "type": self.vehicle_type.value,
174
+ "speed_kmh": self.speed_kmh,
175
+ "direction": self.direction.value,
176
+ "direction_angle": self.direction_angle,
177
+ "action": self.action.value,
178
+ "braking": self.braking,
179
+ "signaling": self.signaling,
180
+ "lights_on": self.lights_on,
181
+ "horn_used": self.horn_used,
182
+ "path": self.path,
183
+ "description": self.description
184
+ }
185
+
186
+
187
+ @dataclass
188
+ class AccidentDetails:
189
+ """Details about the accident."""
190
+ accident_type: AccidentType = AccidentType.SIDE_IMPACT
191
+ severity: Severity = Severity.MODERATE
192
+ collision_angle: float = 90.0
193
+ collision_point: Tuple[float, float] = (0.0, 0.0)
194
+ contributing_factors: List[str] = field(default_factory=list)
195
+ fault_vehicle: int = 0 # 0 = undetermined, 1 or 2
196
+
197
+ def to_dict(self) -> Dict[str, Any]:
198
+ return {
199
+ "type": self.accident_type.value,
200
+ "severity": self.severity.value,
201
+ "collision_angle": self.collision_angle,
202
+ "collision_point": list(self.collision_point),
203
+ "contributing_factors": self.contributing_factors,
204
+ "fault_vehicle": self.fault_vehicle
205
+ }
206
+
207
+
208
+ @dataclass
209
+ class ScenarioMetrics:
210
+ """Metrics for an accident scenario."""
211
+ collision_probability: float = 0.5
212
+ path_overlap: float = 0.5
213
+ speed_differential: float = 0.0
214
+ time_to_collision: float = 0.0
215
+ impact_force_estimate: float = 0.0
216
+
217
+ def to_dict(self) -> Dict[str, Any]:
218
+ return {
219
+ "collision_probability": self.collision_probability,
220
+ "path_overlap": self.path_overlap,
221
+ "speed_differential": self.speed_differential,
222
+ "time_to_collision": self.time_to_collision,
223
+ "impact_force_estimate": self.impact_force_estimate
224
+ }
225
+
226
+
227
+ @dataclass
228
+ class Scenario:
229
+ """A single accident scenario."""
230
+ scenario_id: int = 0
231
+ accident_type: AccidentType = AccidentType.SIDE_IMPACT
232
+ probability: float = 0.5
233
+ description: str = ""
234
+ contributing_factors: List[str] = field(default_factory=list)
235
+ metrics: ScenarioMetrics = field(default_factory=ScenarioMetrics)
236
+ vehicle_1_path: List[Tuple[float, float]] = field(default_factory=list)
237
+ vehicle_2_path: List[Tuple[float, float]] = field(default_factory=list)
238
+ collision_point: Tuple[float, float] = (0.0, 0.0)
239
+
240
+ def to_dict(self) -> Dict[str, Any]:
241
+ return {
242
+ "id": self.scenario_id,
243
+ "accident_type": self.accident_type.value,
244
+ "probability": self.probability,
245
+ "description": self.description,
246
+ "contributing_factors": self.contributing_factors,
247
+ "metrics": self.metrics.to_dict(),
248
+ "vehicle_1_path": self.vehicle_1_path,
249
+ "vehicle_2_path": self.vehicle_2_path,
250
+ "collision_point": list(self.collision_point)
251
+ }
252
+
253
+
254
+ @dataclass
255
+ class FaultAssessment:
256
+ """Preliminary fault assessment."""
257
+ vehicle_1_contribution: float = 50.0 # Percentage
258
+ vehicle_2_contribution: float = 50.0
259
+ likely_at_fault: int = 0 # 0 = undetermined, 1 or 2
260
+ primary_factor: str = ""
261
+ confidence: float = 0.5
262
+
263
+ def to_dict(self) -> Dict[str, Any]:
264
+ return {
265
+ "vehicle_1_contribution": self.vehicle_1_contribution,
266
+ "vehicle_2_contribution": self.vehicle_2_contribution,
267
+ "likely_at_fault": self.likely_at_fault,
268
+ "primary_factor": self.primary_factor,
269
+ "confidence": self.confidence
270
+ }
271
+
272
+
273
+ @dataclass
274
+ class TimelineEvent:
275
+ """An event in the accident timeline."""
276
+ time_offset: float = 0.0 # Seconds relative to collision (negative = before)
277
+ event: str = ""
278
+
279
+ def to_dict(self) -> Dict[str, Any]:
280
+ return {
281
+ "time": self.time_offset,
282
+ "event": self.event
283
+ }
284
+
285
+
286
+ @dataclass
287
+ class AccidentRecord:
288
+ """Complete accident record."""
289
+ accident_id: str = ""
290
+ timestamp: datetime = field(default_factory=datetime.now)
291
+ location: Location = field(default_factory=Location)
292
+ conditions: Conditions = field(default_factory=Conditions)
293
+ vehicle_1: VehicleData = field(default_factory=VehicleData)
294
+ vehicle_2: VehicleData = field(default_factory=VehicleData)
295
+ accident_details: AccidentDetails = field(default_factory=AccidentDetails)
296
+
297
+ def to_dict(self) -> Dict[str, Any]:
298
+ return {
299
+ "accident_id": self.accident_id,
300
+ "timestamp": self.timestamp.isoformat(),
301
+ "location": self.location.to_dict(),
302
+ "conditions": self.conditions.to_dict(),
303
+ "vehicle_1": self.vehicle_1.to_dict(),
304
+ "vehicle_2": self.vehicle_2.to_dict(),
305
+ "accident_details": self.accident_details.to_dict()
306
+ }
307
+
308
+
309
+ @dataclass
310
+ class AnalysisResult:
311
+ """Results from AI analysis."""
312
+ scenarios: List[Scenario] = field(default_factory=list)
313
+ most_likely_scenario_id: int = 0
314
+ overall_collision_probability: float = 0.5
315
+ fault_assessment: FaultAssessment = field(default_factory=FaultAssessment)
316
+ timeline: List[TimelineEvent] = field(default_factory=list)
317
+ analysis_timestamp: datetime = field(default_factory=datetime.now)
318
+
319
+ def to_dict(self) -> Dict[str, Any]:
320
+ return {
321
+ "scenarios": [s.to_dict() for s in self.scenarios],
322
+ "most_likely_scenario": {
323
+ "id": self.most_likely_scenario_id,
324
+ "probability": self.scenarios[self.most_likely_scenario_id - 1].probability
325
+ if self.scenarios else 0
326
+ },
327
+ "overall_collision_probability": self.overall_collision_probability,
328
+ "preliminary_fault_assessment": self.fault_assessment.to_dict(),
329
+ "timeline": [e.to_dict() for e in self.timeline],
330
+ "analysis_timestamp": self.analysis_timestamp.isoformat()
331
+ }
332
+
333
+
334
+ # ============================================================
335
+ # FEATURE VECTOR SCHEMA (for MindSpore)
336
+ # ============================================================
337
+
338
+ FEATURE_SCHEMA = {
339
+ "input_features": [
340
+ # Vehicle 1 features (7)
341
+ "v1_type_encoded", # 0-4 normalized
342
+ "v1_speed_normalized", # 0-1 (speed/200)
343
+ "v1_direction_encoded", # 0-1 (direction/8)
344
+ "v1_angle_normalized", # 0-1 (angle/360)
345
+ "v1_action_encoded", # 0-1 (action/10)
346
+ "v1_braking", # 0 or 1
347
+ "v1_signaling", # 0 or 1
348
+
349
+ # Vehicle 2 features (7)
350
+ "v2_type_encoded",
351
+ "v2_speed_normalized",
352
+ "v2_direction_encoded",
353
+ "v2_angle_normalized",
354
+ "v2_action_encoded",
355
+ "v2_braking",
356
+ "v2_signaling",
357
+
358
+ # Environmental features (3)
359
+ "weather_encoded", # 0-1 (weather/5)
360
+ "road_condition_encoded",# 0-1 (condition/4)
361
+ "visibility", # 0-1
362
+
363
+ # Derived features (6)
364
+ "collision_angle_normalized", # 0-1 (angle/180)
365
+ "speed_differential", # 0-1 (diff/200)
366
+ "combined_speed_normalized", # 0-1 (sum/400)
367
+ "same_direction", # 0 or 1
368
+ "speed_product_normalized", # 0-1
369
+ "angle_difference_normalized" # -1 to 1
370
+ ],
371
+ "total_input_features": 23,
372
+
373
+ "output_labels": [
374
+ "rear_end_collision",
375
+ "side_impact",
376
+ "head_on_collision",
377
+ "sideswipe",
378
+ "roundabout_entry_collision",
379
+ "lane_change_collision",
380
+ "intersection_collision"
381
+ ],
382
+ "total_output_classes": 7
383
+ }
384
+
385
+
386
+ # ============================================================
387
+ # VALIDATION FUNCTIONS
388
+ # ============================================================
389
+
390
+ def validate_speed(speed: float, vehicle_type: VehicleType) -> bool:
391
+ """Validate speed is within acceptable range for vehicle type."""
392
+ max_speeds = {
393
+ VehicleType.SEDAN: 180,
394
+ VehicleType.SUV: 160,
395
+ VehicleType.TRUCK: 120,
396
+ VehicleType.MOTORCYCLE: 200,
397
+ VehicleType.BUS: 100
398
+ }
399
+ return 0 <= speed <= max_speeds.get(vehicle_type, 200)
400
+
401
+
402
+ def validate_coordinates(lat: float, lng: float) -> bool:
403
+ """Validate geographic coordinates."""
404
+ return -90 <= lat <= 90 and -180 <= lng <= 180
405
+
406
+
407
+ def validate_path(path: List[Tuple[float, float]]) -> bool:
408
+ """Validate vehicle path has at least 2 points."""
409
+ if len(path) < 2:
410
+ return False
411
+ return all(validate_coordinates(p[0], p[1]) for p in path)
412
+
413
+
414
+ def validate_probability(prob: float) -> bool:
415
+ """Validate probability is between 0 and 1."""
416
+ return 0 <= prob <= 1
data/synthetic_dataset_generator.py ADDED
@@ -0,0 +1,967 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Synthetic Accident Dataset Generator
3
+ =====================================
4
+ Generates realistic synthetic traffic accident data for training
5
+ the MindSpore AI model.
6
+
7
+ This dataset simulates various accident scenarios at roundabouts
8
+ with different vehicle types, speeds, directions, and conditions.
9
+ """
10
+
11
+ import numpy as np
12
+ import pandas as pd
13
+ import json
14
+ import random
15
+ from datetime import datetime, timedelta
16
+ from pathlib import Path
17
+ from typing import Dict, List, Tuple, Any
18
+
19
+ import sys
20
+ sys.path.insert(0, str(Path(__file__).parent.parent))
21
+
22
+ from config import (
23
+ CASE_STUDY_LOCATION,
24
+ VEHICLE_TYPES,
25
+ ACCIDENT_TYPES,
26
+ CONTRIBUTING_FACTORS,
27
+ ROAD_TYPES,
28
+ DATA_DIR,
29
+ PROCESSED_DATA_DIR
30
+ )
31
+
32
+
33
+ # ============================================================
34
+ # CONSTANTS FOR DATA GENERATION
35
+ # ============================================================
36
+
37
+ # Directions with angles (for roundabout entry/exit)
38
+ DIRECTIONS = {
39
+ 'north': 0,
40
+ 'northeast': 45,
41
+ 'east': 90,
42
+ 'southeast': 135,
43
+ 'south': 180,
44
+ 'southwest': 225,
45
+ 'west': 270,
46
+ 'northwest': 315
47
+ }
48
+
49
+ # Actions vehicles can take
50
+ VEHICLE_ACTIONS = [
51
+ 'going_straight',
52
+ 'turning_left',
53
+ 'turning_right',
54
+ 'entering_roundabout',
55
+ 'exiting_roundabout',
56
+ 'changing_lane_left',
57
+ 'changing_lane_right',
58
+ 'slowing_down',
59
+ 'accelerating',
60
+ 'stopped'
61
+ ]
62
+
63
+ # Weather conditions with probability weights
64
+ WEATHER_CONDITIONS = {
65
+ 'clear': 0.55,
66
+ 'cloudy': 0.20,
67
+ 'rainy': 0.12,
68
+ 'foggy': 0.07,
69
+ 'sandstorm': 0.06
70
+ }
71
+
72
+ # Road conditions with probability weights
73
+ ROAD_CONDITIONS = {
74
+ 'dry': 0.65,
75
+ 'wet': 0.18,
76
+ 'sandy': 0.12,
77
+ 'oily': 0.05
78
+ }
79
+
80
+ # Road types with probability weights (expanded)
81
+ ROAD_TYPE_WEIGHTS = {
82
+ 'roundabout': 0.30,
83
+ 'crossroad': 0.25,
84
+ 't_junction': 0.15,
85
+ 'highway_merge': 0.10,
86
+ 'parking': 0.05,
87
+ 'highway': 0.08,
88
+ 'urban_road': 0.05,
89
+ 'other': 0.02
90
+ }
91
+
92
+ # Time of day distribution (hour: probability)
93
+ TIME_DISTRIBUTION = {
94
+ 'morning_rush': (7, 9, 0.25), # 7-9 AM, 25% of accidents
95
+ 'midday': (10, 15, 0.20), # 10 AM - 3 PM, 20%
96
+ 'evening_rush': (16, 19, 0.30), # 4-7 PM, 30%
97
+ 'night': (20, 23, 0.15), # 8-11 PM, 15%
98
+ 'late_night': (0, 6, 0.10) # Midnight - 6 AM, 10%
99
+ }
100
+
101
+ # Lighting conditions
102
+ LIGHTING_CONDITIONS = ['daylight', 'dusk', 'dawn', 'night_lit', 'night_dark']
103
+
104
+
105
+ # ============================================================
106
+ # DATA SCHEMA DEFINITION
107
+ # ============================================================
108
+
109
+ ACCIDENT_SCHEMA = {
110
+ "accident_id": "string",
111
+ "timestamp": "datetime",
112
+ "location": {
113
+ "name": "string",
114
+ "latitude": "float",
115
+ "longitude": "float",
116
+ "road_type": "string"
117
+ },
118
+ "conditions": {
119
+ "weather": "string",
120
+ "road_condition": "string",
121
+ "visibility": "float", # 0-1 scale
122
+ "lighting": "string" # daylight, dusk, night, artificial
123
+ },
124
+ "vehicle_1": {
125
+ "type": "string",
126
+ "speed_kmh": "float",
127
+ "direction": "string",
128
+ "direction_angle": "float",
129
+ "action": "string",
130
+ "braking": "boolean",
131
+ "signaling": "boolean",
132
+ "path": "list[tuple]"
133
+ },
134
+ "vehicle_2": {
135
+ "type": "string",
136
+ "speed_kmh": "float",
137
+ "direction": "string",
138
+ "direction_angle": "float",
139
+ "action": "string",
140
+ "braking": "boolean",
141
+ "signaling": "boolean",
142
+ "path": "list[tuple]"
143
+ },
144
+ "accident_details": {
145
+ "type": "string",
146
+ "severity": "string", # minor, moderate, severe
147
+ "collision_angle": "float",
148
+ "collision_point": "tuple",
149
+ "contributing_factors": "list[string]",
150
+ "fault_vehicle": "int" # 1 or 2
151
+ },
152
+ "outcomes": {
153
+ "scenario_probability": "float",
154
+ "damage_estimate": "string",
155
+ "injuries": "boolean"
156
+ }
157
+ }
158
+
159
+
160
+ # ============================================================
161
+ # HELPER FUNCTIONS
162
+ # ============================================================
163
+
164
+ def generate_accident_id() -> str:
165
+ """Generate unique accident ID."""
166
+ timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
167
+ random_suffix = ''.join(random.choices('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', k=4))
168
+ return f"ACC-{timestamp}-{random_suffix}"
169
+
170
+
171
+ def generate_timestamp() -> datetime:
172
+ """Generate realistic accident timestamp based on distribution."""
173
+ # Select time period based on weights
174
+ period = random.choices(
175
+ list(TIME_DISTRIBUTION.keys()),
176
+ weights=[v[2] for v in TIME_DISTRIBUTION.values()]
177
+ )[0]
178
+
179
+ start_hour, end_hour, _ = TIME_DISTRIBUTION[period]
180
+
181
+ # Generate random date within last year
182
+ days_ago = random.randint(0, 365)
183
+ base_date = datetime.now() - timedelta(days=days_ago)
184
+
185
+ # Generate random time within period
186
+ hour = random.randint(start_hour, end_hour)
187
+ minute = random.randint(0, 59)
188
+
189
+ return base_date.replace(hour=hour, minute=minute, second=0, microsecond=0)
190
+
191
+
192
+ def select_weather() -> Tuple[str, float]:
193
+ """Select weather condition and corresponding visibility."""
194
+ weather = random.choices(
195
+ list(WEATHER_CONDITIONS.keys()),
196
+ weights=list(WEATHER_CONDITIONS.values())
197
+ )[0]
198
+
199
+ visibility_map = {
200
+ 'clear': random.uniform(0.9, 1.0),
201
+ 'cloudy': random.uniform(0.8, 0.95),
202
+ 'rainy': random.uniform(0.5, 0.8),
203
+ 'foggy': random.uniform(0.2, 0.5),
204
+ 'sandstorm': random.uniform(0.1, 0.4)
205
+ }
206
+
207
+ return weather, visibility_map[weather]
208
+
209
+
210
+ def select_road_condition(weather: str) -> str:
211
+ """Select road condition based on weather."""
212
+ if weather == 'rainy':
213
+ return 'wet'
214
+ elif weather == 'sandstorm':
215
+ return random.choice(['sandy', 'dry'])
216
+ else:
217
+ return random.choices(
218
+ list(ROAD_CONDITIONS.keys()),
219
+ weights=list(ROAD_CONDITIONS.values())
220
+ )[0]
221
+
222
+
223
+ def generate_vehicle_data(vehicle_num: int, accident_type: str, road_type: str = 'roundabout') -> Dict:
224
+ """Generate realistic vehicle data based on accident type and road type."""
225
+
226
+ # Select vehicle type with realistic distribution
227
+ vehicle_type = random.choices(
228
+ list(VEHICLE_TYPES.keys()),
229
+ weights=[0.50, 0.30, 0.10, 0.05, 0.05] # sedan most common
230
+ )[0]
231
+
232
+ specs = VEHICLE_TYPES[vehicle_type]
233
+
234
+ # Generate speed based on accident type and road type
235
+ speed_modifier = {
236
+ 'roundabout': 0.6,
237
+ 'crossroad': 0.7,
238
+ 't_junction': 0.65,
239
+ 'highway_merge': 0.9,
240
+ 'parking': 0.2,
241
+ 'highway': 1.0,
242
+ 'urban_road': 0.5,
243
+ 'other': 0.6
244
+ }.get(road_type, 0.6)
245
+
246
+ if accident_type == 'rear_end_collision':
247
+ if vehicle_num == 1:
248
+ speed = random.uniform(20, 50) * speed_modifier
249
+ else:
250
+ speed = random.uniform(40, 80) * speed_modifier
251
+ elif accident_type == 'head_on_collision':
252
+ speed = random.uniform(50, 100) * speed_modifier
253
+ elif accident_type in ['roundabout_entry_collision', 'intersection_collision']:
254
+ speed = random.uniform(30, 60) * speed_modifier
255
+ else:
256
+ speed = random.uniform(30, specs['max_speed'] * 0.7) * speed_modifier
257
+
258
+ # Ensure speed doesn't exceed vehicle max
259
+ speed = min(speed, specs['max_speed'])
260
+
261
+ # Select direction
262
+ direction = random.choice(list(DIRECTIONS.keys()))
263
+
264
+ # Select action based on accident type and road type
265
+ if road_type == 'roundabout':
266
+ if accident_type == 'roundabout_entry_collision':
267
+ action = random.choice(['entering_roundabout', 'going_straight'])
268
+ else:
269
+ action = random.choice(['entering_roundabout', 'exiting_roundabout', 'going_straight'])
270
+ elif road_type in ['crossroad', 't_junction']:
271
+ action = random.choice(['going_straight', 'turning_left', 'turning_right', 'stopped'])
272
+ elif road_type == 'highway_merge':
273
+ action = random.choice(['going_straight', 'changing_lane_left', 'changing_lane_right', 'accelerating'])
274
+ elif road_type == 'parking':
275
+ action = random.choice(['slowing_down', 'stopped', 'going_straight'])
276
+ elif road_type == 'highway':
277
+ action = random.choice(['going_straight', 'changing_lane_left', 'changing_lane_right'])
278
+ else:
279
+ if accident_type == 'lane_change_collision':
280
+ action = random.choice(['changing_lane_left', 'changing_lane_right'])
281
+ elif accident_type == 'rear_end_collision':
282
+ action = 'going_straight' if vehicle_num == 2 else random.choice(['slowing_down', 'stopped'])
283
+ else:
284
+ action = random.choice(VEHICLE_ACTIONS)
285
+
286
+ # Braking and signaling
287
+ braking = random.random() < 0.4 # 40% chance of braking
288
+ signaling = random.random() < 0.3 # 30% chance of signaling
289
+
290
+ # Generate simplified path (entry point, intermediate, collision area)
291
+ path = generate_vehicle_path(direction, accident_type)
292
+
293
+ return {
294
+ 'type': vehicle_type,
295
+ 'speed_kmh': round(speed, 1),
296
+ 'direction': direction,
297
+ 'direction_angle': DIRECTIONS[direction],
298
+ 'action': action,
299
+ 'braking': braking,
300
+ 'signaling': signaling,
301
+ 'path': path
302
+ }
303
+
304
+
305
+ def generate_vehicle_path(direction: str, accident_type: str) -> List[List[float]]:
306
+ """Generate a realistic vehicle path for the roundabout."""
307
+
308
+ base_lat = CASE_STUDY_LOCATION['latitude']
309
+ base_lng = CASE_STUDY_LOCATION['longitude']
310
+
311
+ # Offset based on direction (entry points)
312
+ direction_offsets = {
313
+ 'north': (0.002, 0),
314
+ 'south': (-0.002, 0),
315
+ 'east': (0, 0.002),
316
+ 'west': (0, -0.002),
317
+ 'northeast': (0.0015, 0.0015),
318
+ 'northwest': (0.0015, -0.0015),
319
+ 'southeast': (-0.0015, 0.0015),
320
+ 'southwest': (-0.0015, -0.0015)
321
+ }
322
+
323
+ offset = direction_offsets.get(direction, (0.002, 0))
324
+
325
+ # Generate path points
326
+ start_lat = base_lat + offset[0]
327
+ start_lng = base_lng + offset[1]
328
+
329
+ # Path moves toward center (collision zone)
330
+ path = [
331
+ [start_lat, start_lng],
332
+ [start_lat - offset[0] * 0.5, start_lng - offset[1] * 0.5],
333
+ [base_lat + random.uniform(-0.0003, 0.0003),
334
+ base_lng + random.uniform(-0.0003, 0.0003)]
335
+ ]
336
+
337
+ return path
338
+
339
+
340
+ def calculate_collision_angle(v1_direction: str, v2_direction: str) -> float:
341
+ """Calculate the angle of collision between two vehicles."""
342
+ angle1 = DIRECTIONS[v1_direction]
343
+ angle2 = DIRECTIONS[v2_direction]
344
+
345
+ diff = abs(angle1 - angle2)
346
+ if diff > 180:
347
+ diff = 360 - diff
348
+
349
+ return diff
350
+
351
+
352
+ def determine_accident_type(v1_direction: str, v2_direction: str,
353
+ v1_action: str, v2_action: str,
354
+ road_type: str = 'roundabout') -> str:
355
+ """Determine accident type based on vehicle directions, actions, and road type."""
356
+
357
+ collision_angle = calculate_collision_angle(v1_direction, v2_direction)
358
+
359
+ # Head-on: ~180 degrees
360
+ if collision_angle > 150:
361
+ return 'head_on_collision'
362
+
363
+ # Rear-end: ~0 degrees, same direction
364
+ if collision_angle < 30:
365
+ return 'rear_end_collision'
366
+
367
+ # Side impact: ~90 degrees
368
+ if 60 < collision_angle < 120:
369
+ return 'side_impact'
370
+
371
+ # Roundabout specific
372
+ if road_type == 'roundabout' and ('roundabout' in v1_action or 'roundabout' in v2_action):
373
+ return 'roundabout_entry_collision'
374
+
375
+ # Lane change
376
+ if 'changing_lane' in v1_action or 'changing_lane' in v2_action:
377
+ return 'lane_change_collision'
378
+
379
+ # Intersection/crossroad collision
380
+ if road_type in ['crossroad', 't_junction']:
381
+ return 'intersection_collision'
382
+
383
+ # Default sideswipe for smaller angles
384
+ if 30 <= collision_angle <= 60:
385
+ return 'sideswipe'
386
+
387
+ # Default to intersection collision
388
+ return 'intersection_collision'
389
+
390
+
391
+ def determine_contributing_factors(
392
+ v1_data: Dict,
393
+ v2_data: Dict,
394
+ weather: str,
395
+ road_condition: str,
396
+ road_type: str = 'roundabout'
397
+ ) -> List[str]:
398
+ """Determine contributing factors based on accident data."""
399
+
400
+ factors = []
401
+
402
+ # Speed-related
403
+ speed_limits = {
404
+ 'roundabout': 50, 'crossroad': 60, 't_junction': 50,
405
+ 'highway_merge': 80, 'parking': 20, 'highway': 120, 'urban_road': 50, 'other': 60
406
+ }
407
+ speed_limit = speed_limits.get(road_type, 60)
408
+
409
+ if v1_data['speed_kmh'] > speed_limit or v2_data['speed_kmh'] > speed_limit:
410
+ factors.append('speeding')
411
+
412
+ # Following distance (for similar directions)
413
+ collision_angle = calculate_collision_angle(v1_data['direction'], v2_data['direction'])
414
+ if collision_angle < 30 and abs(v1_data['speed_kmh'] - v2_data['speed_kmh']) > 20:
415
+ factors.append('following_too_closely')
416
+
417
+ # Failure to yield
418
+ if road_type == 'roundabout' and ('roundabout' in v1_data['action'] or 'roundabout' in v2_data['action']):
419
+ factors.append('failure_to_yield')
420
+ elif road_type in ['crossroad', 't_junction']:
421
+ if random.random() < 0.4:
422
+ factors.append('failure_to_yield')
423
+
424
+ # Improper lane change
425
+ if 'changing_lane' in v1_data['action'] or 'changing_lane' in v2_data['action']:
426
+ factors.append('improper_lane_change')
427
+
428
+ # Signaling
429
+ if not v1_data['signaling'] and ('turn' in v1_data['action'] or 'changing' in v1_data['action']):
430
+ factors.append('failure_to_signal')
431
+
432
+ # Weather conditions
433
+ if weather in ['rainy', 'foggy', 'sandstorm']:
434
+ factors.append('weather_conditions')
435
+
436
+ # Road conditions
437
+ if road_condition != 'dry':
438
+ factors.append('road_conditions')
439
+
440
+ # Add some randomness
441
+ random_factors = ['distracted_driving', 'improper_turn', 'running_red_light', 'fatigue']
442
+ if random.random() < 0.3:
443
+ factors.append(random.choice(random_factors))
444
+
445
+ return factors[:4] # Limit to 4 factors
446
+
447
+
448
+ def determine_fault(v1_data: Dict, v2_data: Dict, accident_type: str) -> int:
449
+ """Determine which vehicle is primarily at fault."""
450
+
451
+ v1_score = 0
452
+ v2_score = 0
453
+
454
+ # Speed factor
455
+ if v1_data['speed_kmh'] > v2_data['speed_kmh']:
456
+ v1_score += 1
457
+ else:
458
+ v2_score += 1
459
+
460
+ # Signaling factor
461
+ if not v1_data['signaling']:
462
+ v1_score += 1
463
+ if not v2_data['signaling']:
464
+ v2_score += 1
465
+
466
+ # Braking factor (not braking is worse)
467
+ if not v1_data['braking']:
468
+ v1_score += 1
469
+ if not v2_data['braking']:
470
+ v2_score += 1
471
+
472
+ # Action-based fault
473
+ risky_actions = ['accelerating', 'changing_lane_left', 'changing_lane_right']
474
+ if v1_data['action'] in risky_actions:
475
+ v1_score += 1
476
+ if v2_data['action'] in risky_actions:
477
+ v2_score += 1
478
+
479
+ # Rear-end: usually rear vehicle at fault
480
+ if accident_type == 'rear_end_collision':
481
+ v2_score += 2
482
+
483
+ return 1 if v1_score > v2_score else 2
484
+
485
+
486
+ def calculate_scenario_probability(
487
+ v1_data: Dict,
488
+ v2_data: Dict,
489
+ weather: str,
490
+ road_condition: str,
491
+ accident_type: str,
492
+ road_type: str = 'roundabout'
493
+ ) -> float:
494
+ """Calculate the probability of this accident scenario."""
495
+
496
+ base_prob = 0.5
497
+
498
+ # Road type risk factor
499
+ road_risk = {
500
+ 'roundabout': 0.05, 'crossroad': 0.1, 't_junction': 0.08,
501
+ 'highway_merge': 0.12, 'parking': -0.1, 'highway': 0.15,
502
+ 'urban_road': 0.03, 'other': 0.05
503
+ }
504
+ base_prob += road_risk.get(road_type, 0.05)
505
+
506
+ # Collision angle impact
507
+ collision_angle = calculate_collision_angle(v1_data['direction'], v2_data['direction'])
508
+ if 60 < collision_angle < 120: # Side impact most likely at roundabout
509
+ base_prob += 0.15
510
+ elif collision_angle < 30: # Rear-end
511
+ base_prob += 0.1
512
+
513
+ # Speed impact
514
+ combined_speed = v1_data['speed_kmh'] + v2_data['speed_kmh']
515
+ if combined_speed > 100:
516
+ base_prob += 0.1
517
+ if combined_speed > 150:
518
+ base_prob += 0.1
519
+
520
+ # Weather impact
521
+ weather_impact = {
522
+ 'clear': 0, 'cloudy': 0.02, 'rainy': 0.08,
523
+ 'foggy': 0.1, 'sandstorm': 0.12
524
+ }
525
+ base_prob += weather_impact.get(weather, 0)
526
+
527
+ # Road condition impact
528
+ road_impact = {'dry': 0, 'wet': 0.08, 'sandy': 0.1, 'oily': 0.15}
529
+ base_prob += road_impact.get(road_condition, 0)
530
+
531
+ # Action risk
532
+ risky_actions = ['changing_lane_left', 'changing_lane_right', 'accelerating', 'entering_roundabout']
533
+ if v1_data['action'] in risky_actions:
534
+ base_prob += 0.05
535
+ if v2_data['action'] in risky_actions:
536
+ base_prob += 0.05
537
+
538
+ # Not braking increases risk
539
+ if not v1_data['braking'] and not v2_data['braking']:
540
+ base_prob += 0.05
541
+
542
+ # Add some randomness
543
+ base_prob += random.uniform(-0.1, 0.1)
544
+
545
+ return max(0.1, min(0.95, base_prob))
546
+
547
+
548
+ # ============================================================
549
+ # MAIN DATASET GENERATION
550
+ # ============================================================
551
+
552
+ def generate_single_accident() -> Dict:
553
+ """Generate a single accident record."""
554
+
555
+ # Generate basic info
556
+ accident_id = generate_accident_id()
557
+ timestamp = generate_timestamp()
558
+ weather, visibility = select_weather()
559
+ road_condition = select_road_condition(weather)
560
+
561
+ # Select road type
562
+ road_type = random.choices(
563
+ list(ROAD_TYPE_WEIGHTS.keys()),
564
+ weights=list(ROAD_TYPE_WEIGHTS.values())
565
+ )[0]
566
+
567
+ # Determine lighting based on time
568
+ hour = timestamp.hour
569
+ if 7 <= hour < 17:
570
+ lighting = 'daylight'
571
+ elif hour in [6, 17, 18]:
572
+ lighting = random.choice(['dusk', 'dawn'])
573
+ elif 19 <= hour <= 23 or 0 <= hour < 6:
574
+ lighting = random.choice(['night_lit', 'night_dark'])
575
+ else:
576
+ lighting = 'daylight'
577
+
578
+ # Adjust visibility based on lighting
579
+ if lighting in ['night_dark']:
580
+ visibility = visibility * 0.6
581
+ elif lighting in ['night_lit']:
582
+ visibility = visibility * 0.8
583
+ elif lighting in ['dusk', 'dawn']:
584
+ visibility = visibility * 0.9
585
+
586
+ # Pre-select accident type for more realistic data
587
+ accident_type = random.choice(ACCIDENT_TYPES)
588
+
589
+ # Generate vehicle data
590
+ vehicle_1 = generate_vehicle_data(1, accident_type, road_type)
591
+ vehicle_2 = generate_vehicle_data(2, accident_type, road_type)
592
+
593
+ # Recalculate accident type based on actual vehicle data
594
+ actual_accident_type = determine_accident_type(
595
+ vehicle_1['direction'], vehicle_2['direction'],
596
+ vehicle_1['action'], vehicle_2['action'],
597
+ road_type
598
+ )
599
+
600
+ # Calculate collision details
601
+ collision_angle = calculate_collision_angle(
602
+ vehicle_1['direction'], vehicle_2['direction']
603
+ )
604
+
605
+ # Collision point (near center of roundabout)
606
+ collision_point = [
607
+ CASE_STUDY_LOCATION['latitude'] + random.uniform(-0.0005, 0.0005),
608
+ CASE_STUDY_LOCATION['longitude'] + random.uniform(-0.0005, 0.0005)
609
+ ]
610
+
611
+ # Determine contributing factors
612
+ factors = determine_contributing_factors(
613
+ vehicle_1, vehicle_2, weather, road_condition, road_type
614
+ )
615
+
616
+ # Determine fault
617
+ fault_vehicle = determine_fault(vehicle_1, vehicle_2, actual_accident_type)
618
+
619
+ # Determine severity
620
+ combined_speed = vehicle_1['speed_kmh'] + vehicle_2['speed_kmh']
621
+ if combined_speed > 120:
622
+ severity = 'severe'
623
+ elif combined_speed > 80:
624
+ severity = 'moderate'
625
+ else:
626
+ severity = 'minor'
627
+
628
+ # Calculate probability
629
+ probability = calculate_scenario_probability(
630
+ vehicle_1, vehicle_2, weather, road_condition, actual_accident_type, road_type
631
+ )
632
+
633
+ return {
634
+ 'accident_id': accident_id,
635
+ 'timestamp': timestamp.isoformat(),
636
+ 'location': {
637
+ 'name': CASE_STUDY_LOCATION['name'],
638
+ 'latitude': CASE_STUDY_LOCATION['latitude'],
639
+ 'longitude': CASE_STUDY_LOCATION['longitude'],
640
+ 'road_type': road_type
641
+ },
642
+ 'conditions': {
643
+ 'weather': weather,
644
+ 'road_condition': road_condition,
645
+ 'visibility': round(visibility, 2),
646
+ 'lighting': lighting
647
+ },
648
+ 'vehicle_1': vehicle_1,
649
+ 'vehicle_2': vehicle_2,
650
+ 'accident_details': {
651
+ 'type': actual_accident_type,
652
+ 'severity': severity,
653
+ 'collision_angle': collision_angle,
654
+ 'collision_point': collision_point,
655
+ 'contributing_factors': factors,
656
+ 'fault_vehicle': fault_vehicle
657
+ },
658
+ 'outcomes': {
659
+ 'scenario_probability': round(probability, 3),
660
+ 'damage_estimate': severity,
661
+ 'injuries': severity in ['moderate', 'severe'] and random.random() > 0.4
662
+ }
663
+ }
664
+
665
+
666
+ def generate_dataset(num_samples: int = 1000) -> pd.DataFrame:
667
+ """Generate a complete synthetic accident dataset."""
668
+
669
+ print(f"Generating {num_samples} synthetic accident records...")
670
+
671
+ accidents = []
672
+ for i in range(num_samples):
673
+ if (i + 1) % 1000 == 0:
674
+ print(f" Generated {i + 1}/{num_samples} records...")
675
+
676
+ accident = generate_single_accident()
677
+
678
+ # Flatten for DataFrame
679
+ flat_record = {
680
+ 'accident_id': accident['accident_id'],
681
+ 'timestamp': accident['timestamp'],
682
+ 'location_name': accident['location']['name'],
683
+ 'latitude': accident['location']['latitude'],
684
+ 'longitude': accident['location']['longitude'],
685
+ 'road_type': accident['location']['road_type'],
686
+ 'weather': accident['conditions']['weather'],
687
+ 'road_condition': accident['conditions']['road_condition'],
688
+ 'visibility': accident['conditions']['visibility'],
689
+ 'lighting': accident['conditions']['lighting'],
690
+
691
+ # Vehicle 1
692
+ 'v1_type': accident['vehicle_1']['type'],
693
+ 'v1_speed': accident['vehicle_1']['speed_kmh'],
694
+ 'v1_direction': accident['vehicle_1']['direction'],
695
+ 'v1_direction_angle': accident['vehicle_1']['direction_angle'],
696
+ 'v1_action': accident['vehicle_1']['action'],
697
+ 'v1_braking': accident['vehicle_1']['braking'],
698
+ 'v1_signaling': accident['vehicle_1']['signaling'],
699
+
700
+ # Vehicle 2
701
+ 'v2_type': accident['vehicle_2']['type'],
702
+ 'v2_speed': accident['vehicle_2']['speed_kmh'],
703
+ 'v2_direction': accident['vehicle_2']['direction'],
704
+ 'v2_direction_angle': accident['vehicle_2']['direction_angle'],
705
+ 'v2_action': accident['vehicle_2']['action'],
706
+ 'v2_braking': accident['vehicle_2']['braking'],
707
+ 'v2_signaling': accident['vehicle_2']['signaling'],
708
+
709
+ # Accident details
710
+ 'accident_type': accident['accident_details']['type'],
711
+ 'severity': accident['accident_details']['severity'],
712
+ 'collision_angle': accident['accident_details']['collision_angle'],
713
+ 'contributing_factors': ','.join(accident['accident_details']['contributing_factors']),
714
+ 'fault_vehicle': accident['accident_details']['fault_vehicle'],
715
+
716
+ # Outcomes
717
+ 'scenario_probability': accident['outcomes']['scenario_probability'],
718
+ 'injuries': accident['outcomes']['injuries']
719
+ }
720
+
721
+ accidents.append(flat_record)
722
+
723
+ df = pd.DataFrame(accidents)
724
+ print(f"Dataset generated with {len(df)} records.")
725
+
726
+ return df
727
+
728
+
729
+ def generate_training_features(df: pd.DataFrame) -> Tuple[np.ndarray, np.ndarray]:
730
+ """
731
+ Convert dataset to feature vectors for MindSpore training.
732
+
733
+ Features (32 total):
734
+ - Vehicle 1: type, speed, direction, angle, action, braking, signaling (7)
735
+ - Vehicle 2: type, speed, direction, angle, action, braking, signaling (7)
736
+ - Environment: weather, road_condition, visibility, lighting, road_type (5)
737
+ - Derived: collision_angle, speed_diff, combined_speed, same_direction,
738
+ speed_product, angle_diff, time_of_day, risk_score,
739
+ v1_action_risk, v2_action_risk, relative_speed, approach_rate (12)
740
+ - Total: 31 input features
741
+
742
+ Returns:
743
+ X: Feature matrix (N x 31)
744
+ y: Labels - accident type encoded (N,)
745
+ """
746
+
747
+ # Encode categorical variables
748
+ direction_encoding = {d: i for i, d in enumerate(DIRECTIONS.keys())}
749
+ action_encoding = {a: i for i, a in enumerate(VEHICLE_ACTIONS)}
750
+ vehicle_encoding = {v: i for i, v in enumerate(VEHICLE_TYPES.keys())}
751
+ weather_encoding = {'clear': 0, 'cloudy': 1, 'rainy': 2, 'foggy': 3, 'sandstorm': 4}
752
+ road_encoding = {'dry': 0, 'wet': 1, 'sandy': 2, 'oily': 3}
753
+ road_type_encoding = {
754
+ 'roundabout': 0, 'crossroad': 1, 't_junction': 2, 'highway_merge': 3,
755
+ 'parking': 4, 'highway': 5, 'urban_road': 6, 'other': 7
756
+ }
757
+ lighting_encoding = {'daylight': 0, 'dusk': 1, 'dawn': 2, 'night_lit': 3, 'night_dark': 4}
758
+ accident_encoding = {a: i for i, a in enumerate(ACCIDENT_TYPES)}
759
+
760
+ # Action risk scores
761
+ action_risk = {
762
+ 'going_straight': 0.3, 'turning_left': 0.5, 'turning_right': 0.4,
763
+ 'entering_roundabout': 0.6, 'exiting_roundabout': 0.5,
764
+ 'changing_lane_left': 0.7, 'changing_lane_right': 0.7,
765
+ 'slowing_down': 0.4, 'accelerating': 0.6, 'stopped': 0.2
766
+ }
767
+
768
+ features = []
769
+ labels = []
770
+
771
+ for _, row in df.iterrows():
772
+ # Extract time of day (hour) from timestamp if available
773
+ try:
774
+ hour = pd.to_datetime(row['timestamp']).hour
775
+ except:
776
+ hour = 12 # Default to noon
777
+
778
+ # Calculate derived features
779
+ v1_speed = row['v1_speed']
780
+ v2_speed = row['v2_speed']
781
+ v1_angle = row['v1_direction_angle']
782
+ v2_angle = row['v2_direction_angle']
783
+ collision_angle = row['collision_angle']
784
+
785
+ speed_diff = abs(v1_speed - v2_speed)
786
+ combined_speed = v1_speed + v2_speed
787
+ same_direction = 1 if row['v1_direction'] == row['v2_direction'] else 0
788
+ speed_product = v1_speed * v2_speed
789
+ angle_diff = (v1_angle - v2_angle) % 360
790
+ if angle_diff > 180:
791
+ angle_diff = 360 - angle_diff
792
+
793
+ # Risk score based on conditions
794
+ weather_risk = {'clear': 0.1, 'cloudy': 0.2, 'rainy': 0.5, 'foggy': 0.7, 'sandstorm': 0.8}
795
+ road_risk = {'dry': 0.1, 'wet': 0.5, 'sandy': 0.6, 'oily': 0.8}
796
+ base_risk = weather_risk.get(row['weather'], 0.3) + road_risk.get(row['road_condition'], 0.3)
797
+
798
+ # Relative speed (closing speed)
799
+ if angle_diff > 90: # Approaching
800
+ relative_speed = v1_speed + v2_speed
801
+ else: # Same direction
802
+ relative_speed = abs(v1_speed - v2_speed)
803
+
804
+ # Approach rate (how quickly vehicles are approaching collision)
805
+ approach_rate = relative_speed * (1 - row['visibility']) * (1 + base_risk)
806
+
807
+ feature_vector = [
808
+ # Vehicle 1 features (7)
809
+ vehicle_encoding.get(row['v1_type'], 0) / 5,
810
+ v1_speed / 200, # Normalize speed
811
+ direction_encoding.get(row['v1_direction'], 0) / 8,
812
+ v1_angle / 360,
813
+ action_encoding.get(row['v1_action'], 0) / 10,
814
+ 1 if row['v1_braking'] else 0,
815
+ 1 if row['v1_signaling'] else 0,
816
+
817
+ # Vehicle 2 features (7)
818
+ vehicle_encoding.get(row['v2_type'], 0) / 5,
819
+ v2_speed / 200,
820
+ direction_encoding.get(row['v2_direction'], 0) / 8,
821
+ v2_angle / 360,
822
+ action_encoding.get(row['v2_action'], 0) / 10,
823
+ 1 if row['v2_braking'] else 0,
824
+ 1 if row['v2_signaling'] else 0,
825
+
826
+ # Environmental features (5)
827
+ weather_encoding.get(row['weather'], 0) / 5,
828
+ road_encoding.get(row['road_condition'], 0) / 4,
829
+ row['visibility'],
830
+ lighting_encoding.get(row.get('lighting', 'daylight'), 0) / 5,
831
+ road_type_encoding.get(row.get('road_type', 'roundabout'), 0) / 8,
832
+
833
+ # Derived features (12)
834
+ collision_angle / 180,
835
+ speed_diff / 200,
836
+ combined_speed / 400,
837
+ same_direction,
838
+ speed_product / 40000,
839
+ angle_diff / 180,
840
+ hour / 24, # Time of day
841
+ base_risk, # Risk score
842
+ action_risk.get(row['v1_action'], 0.5), # V1 action risk
843
+ action_risk.get(row['v2_action'], 0.5), # V2 action risk
844
+ relative_speed / 400, # Relative/closing speed
845
+ min(approach_rate / 200, 1.0), # Approach rate (capped)
846
+ ]
847
+
848
+ features.append(feature_vector)
849
+ labels.append(accident_encoding.get(row['accident_type'], 0))
850
+
851
+ X = np.array(features, dtype=np.float32)
852
+ y = np.array(labels, dtype=np.int32)
853
+
854
+ return X, y
855
+
856
+
857
+ def generate_training_features_extended(df: pd.DataFrame) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
858
+ """
859
+ Extended feature generation that also outputs probability targets.
860
+
861
+ Returns:
862
+ X: Feature matrix
863
+ y_class: Classification labels (accident type)
864
+ y_prob: Probability targets (for regression)
865
+ """
866
+ X, y_class = generate_training_features(df)
867
+ y_prob = df['scenario_probability'].values.astype(np.float32)
868
+
869
+ return X, y_class, y_prob
870
+
871
+
872
+ def save_dataset(df: pd.DataFrame, filename: str = "synthetic_accidents"):
873
+ """Save the dataset in multiple formats."""
874
+
875
+ # Create directories if needed
876
+ DATA_DIR.mkdir(parents=True, exist_ok=True)
877
+ PROCESSED_DATA_DIR.mkdir(parents=True, exist_ok=True)
878
+
879
+ # Save as CSV
880
+ csv_path = PROCESSED_DATA_DIR / f"{filename}.csv"
881
+ df.to_csv(csv_path, index=False)
882
+ print(f"Saved CSV: {csv_path}")
883
+
884
+ # Save as JSON (full records)
885
+ json_path = PROCESSED_DATA_DIR / f"{filename}.json"
886
+ df.to_json(json_path, orient='records', indent=2)
887
+ print(f"Saved JSON: {json_path}")
888
+
889
+ # Generate and save training features
890
+ X, y = generate_training_features(df)
891
+
892
+ np_path = PROCESSED_DATA_DIR / f"{filename}_features.npz"
893
+ np.savez(np_path, X=X, y=y)
894
+ print(f"Saved NumPy features: {np_path}")
895
+
896
+ # Save schema
897
+ schema_path = DATA_DIR / "accident_schema.json"
898
+ with open(schema_path, 'w') as f:
899
+ json.dump(ACCIDENT_SCHEMA, f, indent=2)
900
+ print(f"Saved schema: {schema_path}")
901
+
902
+ return csv_path, json_path, np_path
903
+
904
+
905
+ def print_dataset_statistics(df: pd.DataFrame):
906
+ """Print statistics about the generated dataset."""
907
+
908
+ print("\n" + "="*60)
909
+ print("DATASET STATISTICS")
910
+ print("="*60)
911
+
912
+ print(f"\nTotal records: {len(df)}")
913
+
914
+ print(f"\n--- Accident Types ---")
915
+ print(df['accident_type'].value_counts())
916
+
917
+ print(f"\n--- Weather Conditions ---")
918
+ print(df['weather'].value_counts())
919
+
920
+ print(f"\n--- Road Conditions ---")
921
+ print(df['road_condition'].value_counts())
922
+
923
+ print(f"\n--- Severity Distribution ---")
924
+ print(df['severity'].value_counts())
925
+
926
+ print(f"\n--- Vehicle Types (V1) ---")
927
+ print(df['v1_type'].value_counts())
928
+
929
+ print(f"\n--- Speed Statistics ---")
930
+ print(f"V1 Speed: Mean={df['v1_speed'].mean():.1f}, Std={df['v1_speed'].std():.1f}")
931
+ print(f"V2 Speed: Mean={df['v2_speed'].mean():.1f}, Std={df['v2_speed'].std():.1f}")
932
+
933
+ print(f"\n--- Fault Distribution ---")
934
+ print(df['fault_vehicle'].value_counts())
935
+
936
+ print(f"\n--- Injuries ---")
937
+ print(df['injuries'].value_counts())
938
+
939
+ print("\n" + "="*60)
940
+
941
+
942
+ # ============================================================
943
+ # MAIN EXECUTION
944
+ # ============================================================
945
+
946
+ if __name__ == "__main__":
947
+ print("="*60)
948
+ print("SYNTHETIC ACCIDENT DATASET GENERATOR")
949
+ print("Huawei AI Innovation Challenge 2026")
950
+ print("="*60)
951
+
952
+ # Generate dataset
953
+ df = generate_dataset(num_samples=1000)
954
+
955
+ # Print statistics
956
+ print_dataset_statistics(df)
957
+
958
+ # Save dataset
959
+ csv_path, json_path, np_path = save_dataset(df)
960
+
961
+ print("\n" + "="*60)
962
+ print("DATASET GENERATION COMPLETE!")
963
+ print("="*60)
964
+ print(f"\nFiles saved:")
965
+ print(f" - {csv_path}")
966
+ print(f" - {json_path}")
967
+ print(f" - {np_path}")