|
|
""" |
|
|
Traffic Accident Reconstruction System |
|
|
====================================== |
|
|
Main Streamlit Application |
|
|
Huawei AI Innovation Challenge 2026 |
|
|
|
|
|
This system uses MindSpore AI to analyze traffic accidents |
|
|
and generate probable scenarios with 2D simulation. |
|
|
""" |
|
|
|
|
|
import streamlit as st |
|
|
import sys |
|
|
from pathlib import Path |
|
|
|
|
|
|
|
|
sys.path.insert(0, str(Path(__file__).parent)) |
|
|
|
|
|
from config import STREAMLIT_CONFIG, CASE_STUDY_LOCATION, COLORS |
|
|
from ui.components import render_sidebar, render_header, render_footer |
|
|
from ui.map_viewer import render_map_section |
|
|
from ui.vehicle_input import render_vehicle_input |
|
|
from ui.party_input import render_party_input, render_evidence_upload, render_party_summary |
|
|
from ui.results_display import render_results |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
st.set_page_config( |
|
|
page_title=STREAMLIT_CONFIG["page_title"], |
|
|
page_icon=STREAMLIT_CONFIG["page_icon"], |
|
|
layout=STREAMLIT_CONFIG["layout"], |
|
|
initial_sidebar_state=STREAMLIT_CONFIG["initial_sidebar_state"] |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
st.markdown(""" |
|
|
<style> |
|
|
/* Layout - Dark theme with modern spacing */ |
|
|
.block-container { |
|
|
padding-top: 1.4rem; |
|
|
padding-bottom: 2.5rem; |
|
|
max-width: 1200px; |
|
|
} |
|
|
|
|
|
[data-testid="stSidebar"] { |
|
|
min-width: 340px; |
|
|
max-width: 340px; |
|
|
background: linear-gradient(180deg, #0e1117 0%, #1a1f2e 100%); |
|
|
} |
|
|
|
|
|
/* Main container with gradient background */ |
|
|
.main { |
|
|
background: linear-gradient(135deg, #0a0e27 0%, #1a1f3a 50%, #0f1419 100%); |
|
|
color: rgba(255, 255, 255, 0.95); |
|
|
} |
|
|
|
|
|
/* Header styling - Modern minimal */ |
|
|
.main-header { |
|
|
background: linear-gradient(135deg, rgba(30, 58, 95, 0.3) 0%, rgba(45, 90, 135, 0.2) 100%); |
|
|
padding: 2rem; |
|
|
border-radius: 16px; |
|
|
border: 1px solid rgba(255, 255, 255, 0.12); |
|
|
color: white; |
|
|
margin-bottom: 2rem; |
|
|
text-align: center; |
|
|
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3); |
|
|
} |
|
|
|
|
|
.main-header h1 { |
|
|
margin: 0; |
|
|
font-size: 2.8rem; |
|
|
font-weight: 800; |
|
|
letter-spacing: 0.5px; |
|
|
background: linear-gradient(135deg, #60a5fa 0%, #3b82f6 100%); |
|
|
-webkit-background-clip: text; |
|
|
-webkit-text-fill-color: transparent; |
|
|
} |
|
|
|
|
|
.main-header p { |
|
|
margin: 0.8rem 0 0 0; |
|
|
opacity: 0.75; |
|
|
font-size: 1.05rem; |
|
|
color: rgba(255, 255, 255, 0.72); |
|
|
} |
|
|
|
|
|
/* Card styling - Glass morphism effect */ |
|
|
.info-card { |
|
|
background: rgba(255, 255, 255, 0.04); |
|
|
backdrop-filter: blur(10px); |
|
|
padding: 1.5rem; |
|
|
border-radius: 16px; |
|
|
border: 1px solid rgba(255, 255, 255, 0.12); |
|
|
margin: 1rem 0; |
|
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15); |
|
|
} |
|
|
|
|
|
/* Step indicator - Modern pills */ |
|
|
.step-indicator { |
|
|
display: flex; |
|
|
justify-content: space-between; |
|
|
margin: 2rem 0; |
|
|
gap: 0.5rem; |
|
|
} |
|
|
|
|
|
.step { |
|
|
flex: 1; |
|
|
text-align: center; |
|
|
padding: 1rem; |
|
|
background: rgba(255, 255, 255, 0.05); |
|
|
border: 1px solid rgba(255, 255, 255, 0.1); |
|
|
margin: 0; |
|
|
border-radius: 12px; |
|
|
position: relative; |
|
|
transition: all 0.3s ease; |
|
|
} |
|
|
|
|
|
.step.active { |
|
|
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%); |
|
|
border-color: #3b82f6; |
|
|
color: white; |
|
|
box-shadow: 0 8px 24px rgba(59, 130, 246, 0.3); |
|
|
} |
|
|
|
|
|
.step.completed { |
|
|
background: linear-gradient(135deg, #10b981 0%, #059669 100%); |
|
|
border-color: #10b981; |
|
|
color: white; |
|
|
} |
|
|
|
|
|
/* Vehicle cards - Dark theme */ |
|
|
.vehicle-card { |
|
|
background: rgba(255, 255, 255, 0.06); |
|
|
border: 1px solid rgba(255, 255, 255, 0.15); |
|
|
border-radius: 12px; |
|
|
padding: 1.5rem; |
|
|
margin: 1rem 0; |
|
|
} |
|
|
|
|
|
.vehicle-card.vehicle-1 { |
|
|
border-color: rgba(255, 75, 75, 0.4); |
|
|
background: rgba(255, 75, 75, 0.08); |
|
|
} |
|
|
|
|
|
.vehicle-card.vehicle-2 { |
|
|
border-color: rgba(75, 123, 255, 0.4); |
|
|
background: rgba(75, 123, 255, 0.08); |
|
|
} |
|
|
|
|
|
/* Results styling - Modern cards */ |
|
|
.scenario-card { |
|
|
background: rgba(255, 255, 255, 0.06); |
|
|
border-radius: 12px; |
|
|
padding: 1.5rem; |
|
|
margin: 1rem 0; |
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.2); |
|
|
border: 1px solid rgba(255, 255, 255, 0.1); |
|
|
} |
|
|
|
|
|
.probability-high { |
|
|
color: #10b981; |
|
|
font-weight: bold; |
|
|
} |
|
|
|
|
|
.probability-medium { |
|
|
color: #f59e0b; |
|
|
font-weight: bold; |
|
|
} |
|
|
|
|
|
.probability-low { |
|
|
color: #ef4444; |
|
|
font-weight: bold; |
|
|
} |
|
|
|
|
|
/* Button styling - Modern gradient */ |
|
|
.stButton > button { |
|
|
width: 100%; |
|
|
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%); |
|
|
color: white; |
|
|
border: none; |
|
|
padding: 0.85rem 1.5rem; |
|
|
border-radius: 12px; |
|
|
font-weight: 600; |
|
|
font-size: 1rem; |
|
|
transition: all 0.3s ease; |
|
|
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3); |
|
|
} |
|
|
|
|
|
.stButton > button:hover { |
|
|
background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%); |
|
|
box-shadow: 0 6px 16px rgba(59, 130, 246, 0.4); |
|
|
transform: translateY(-2px); |
|
|
} |
|
|
|
|
|
/* Input fields - Dark theme with better contrast */ |
|
|
.stTextInput > div > div > input, |
|
|
.stTextArea > div > div > textarea, |
|
|
.stSelectbox > div > div > select, |
|
|
.stNumberInput > div > div > input { |
|
|
background: rgba(255, 255, 255, 0.08) !important; |
|
|
border: 1px solid rgba(255, 255, 255, 0.15) !important; |
|
|
border-radius: 10px !important; |
|
|
color: white !important; |
|
|
padding: 0.75rem !important; |
|
|
} |
|
|
|
|
|
.stTextInput > div > div > input:focus, |
|
|
.stTextArea > div > div > textarea:focus, |
|
|
.stSelectbox > div > div > select:focus, |
|
|
.stNumberInput > div > div > input:focus { |
|
|
border-color: #3b82f6 !important; |
|
|
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15) !important; |
|
|
} |
|
|
|
|
|
/* Labels - Better visibility */ |
|
|
label, .stTextInput > label, .stTextArea > label, .stSelectbox > label, .stSlider > label, .stNumberInput > label { |
|
|
color: rgba(255, 255, 255, 0.85) !important; |
|
|
font-weight: 500 !important; |
|
|
font-size: 0.95rem !important; |
|
|
} |
|
|
|
|
|
/* Metric cards - Modern design */ |
|
|
[data-testid="stMetricValue"] { |
|
|
font-size: 2.2rem; |
|
|
font-weight: 700; |
|
|
color: white; |
|
|
} |
|
|
|
|
|
[data-testid="stMetricLabel"] { |
|
|
color: rgba(255, 255, 255, 0.7); |
|
|
font-size: 0.95rem; |
|
|
font-weight: 500; |
|
|
} |
|
|
|
|
|
/* Tabs styling - Modern */ |
|
|
.stTabs [data-baseweb="tab-list"] { |
|
|
gap: 8px; |
|
|
background: rgba(255, 255, 255, 0.03); |
|
|
padding: 0.5rem; |
|
|
border-radius: 12px; |
|
|
} |
|
|
|
|
|
.stTabs [data-baseweb="tab"] { |
|
|
background: transparent; |
|
|
border-radius: 8px; |
|
|
color: rgba(255, 255, 255, 0.7); |
|
|
font-weight: 500; |
|
|
padding: 0.75rem 1.5rem; |
|
|
border: 1px solid transparent; |
|
|
} |
|
|
|
|
|
.stTabs [aria-selected="true"] { |
|
|
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%); |
|
|
color: white !important; |
|
|
border-color: #3b82f6; |
|
|
} |
|
|
|
|
|
/* Messages - Modern alerts */ |
|
|
.stSuccess, .element-container:has(.stSuccess) { |
|
|
background: rgba(16, 185, 129, 0.15) !important; |
|
|
border: 1px solid rgba(16, 185, 129, 0.3) !important; |
|
|
border-radius: 12px !important; |
|
|
padding: 1rem !important; |
|
|
} |
|
|
|
|
|
.stSuccess [data-testid="stMarkdownContainer"] p { |
|
|
color: #10b981 !important; |
|
|
} |
|
|
|
|
|
.stError { |
|
|
background: rgba(239, 68, 68, 0.15) !important; |
|
|
border: 1px solid rgba(239, 68, 68, 0.3) !important; |
|
|
border-radius: 12px !important; |
|
|
} |
|
|
|
|
|
.stWarning { |
|
|
background: rgba(245, 158, 11, 0.15) !important; |
|
|
border: 1px solid rgba(245, 158, 11, 0.3) !important; |
|
|
border-radius: 12px !important; |
|
|
} |
|
|
|
|
|
.stInfo { |
|
|
background: rgba(59, 130, 246, 0.15) !important; |
|
|
border: 1px solid rgba(59, 130, 246, 0.3) !important; |
|
|
border-radius: 12px !important; |
|
|
} |
|
|
|
|
|
/* Sidebar - Better styling */ |
|
|
[data-testid="stSidebar"] h1, [data-testid="stSidebar"] h2, [data-testid="stSidebar"] h3 { |
|
|
color: white !important; |
|
|
} |
|
|
|
|
|
[data-testid="stSidebar"] p, [data-testid="stSidebar"] span { |
|
|
color: rgba(255, 255, 255, 0.85) !important; |
|
|
} |
|
|
|
|
|
/* Expander - Modern style */ |
|
|
.streamlit-expanderHeader { |
|
|
background: rgba(255, 255, 255, 0.05) !important; |
|
|
border-radius: 10px !important; |
|
|
border: 1px solid rgba(255, 255, 255, 0.1) !important; |
|
|
color: white !important; |
|
|
font-weight: 500 !important; |
|
|
} |
|
|
|
|
|
.streamlit-expanderContent { |
|
|
background: rgba(255, 255, 255, 0.02) !important; |
|
|
border: 1px solid rgba(255, 255, 255, 0.08) !important; |
|
|
border-radius: 0 0 10px 10px !important; |
|
|
} |
|
|
|
|
|
/* Checkbox and Radio - Better visibility */ |
|
|
.stCheckbox label, .stRadio label { |
|
|
color: rgba(255, 255, 255, 0.85) !important; |
|
|
} |
|
|
|
|
|
/* Slider - Modern */ |
|
|
.stSlider [data-baseweb="slider"] { |
|
|
background: rgba(59, 130, 246, 0.2); |
|
|
} |
|
|
|
|
|
/* Headings - Better hierarchy */ |
|
|
h1, h2, h3, h4, h5, h6 { |
|
|
color: white !important; |
|
|
} |
|
|
|
|
|
h1 { |
|
|
font-weight: 800 !important; |
|
|
letter-spacing: -0.5px; |
|
|
} |
|
|
|
|
|
h2 { |
|
|
font-weight: 700 !important; |
|
|
letter-spacing: -0.3px; |
|
|
} |
|
|
|
|
|
h3 { |
|
|
font-weight: 600 !important; |
|
|
} |
|
|
|
|
|
/* Markdown text - Better readability */ |
|
|
p, span, div { |
|
|
color: rgba(255, 255, 255, 0.9); |
|
|
} |
|
|
|
|
|
/* Code blocks - Dark theme */ |
|
|
code { |
|
|
background: rgba(255, 255, 255, 0.08) !important; |
|
|
color: #60a5fa !important; |
|
|
padding: 0.2rem 0.4rem !important; |
|
|
border-radius: 4px !important; |
|
|
} |
|
|
|
|
|
/* Dataframe - Dark theme */ |
|
|
[data-testid="stDataFrame"] { |
|
|
background: rgba(255, 255, 255, 0.05); |
|
|
border-radius: 12px; |
|
|
border: 1px solid rgba(255, 255, 255, 0.1); |
|
|
} |
|
|
|
|
|
/* Hide Streamlit branding */ |
|
|
#MainMenu {visibility: hidden;} |
|
|
footer {visibility: hidden;} |
|
|
|
|
|
/* Progress bar */ |
|
|
.stProgress > div > div { |
|
|
background: linear-gradient(90deg, #3b82f6 0%, #8b5cf6 100%); |
|
|
border-radius: 8px; |
|
|
} |
|
|
</style> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def init_session_state(): |
|
|
"""Initialize all session state variables.""" |
|
|
|
|
|
|
|
|
if 'current_step' not in st.session_state: |
|
|
st.session_state.current_step = 1 |
|
|
|
|
|
|
|
|
if 'accident_info' not in st.session_state: |
|
|
st.session_state.accident_info = { |
|
|
'location': CASE_STUDY_LOCATION.copy(), |
|
|
'datetime': None, |
|
|
'road_type': 'roundabout', |
|
|
'weather': 'clear', |
|
|
'road_condition': 'dry', |
|
|
'visibility': 1.0, |
|
|
'lighting': 'daylight', |
|
|
'notes': '' |
|
|
} |
|
|
|
|
|
|
|
|
if 'vehicle_1' not in st.session_state: |
|
|
st.session_state.vehicle_1 = { |
|
|
'type': 'sedan', |
|
|
'speed': 50, |
|
|
'direction': 'north', |
|
|
'action': 'entering_roundabout', |
|
|
'braking': False, |
|
|
'signaling': False, |
|
|
'lights_on': True, |
|
|
'horn_used': False, |
|
|
'path': [], |
|
|
'description': '' |
|
|
} |
|
|
|
|
|
|
|
|
if 'vehicle_2' not in st.session_state: |
|
|
st.session_state.vehicle_2 = { |
|
|
'type': 'sedan', |
|
|
'speed': 50, |
|
|
'direction': 'east', |
|
|
'action': 'going_straight', |
|
|
'braking': False, |
|
|
'signaling': False, |
|
|
'lights_on': True, |
|
|
'horn_used': False, |
|
|
'path': [], |
|
|
'description': '' |
|
|
} |
|
|
|
|
|
|
|
|
if 'party_1' not in st.session_state: |
|
|
st.session_state.party_1 = { |
|
|
'full_name': '', |
|
|
'id_iqama': '', |
|
|
'phone': '', |
|
|
'role': 'Driver', |
|
|
'vehicle_make_model': '', |
|
|
'plate_number': '', |
|
|
'insurance': '', |
|
|
'damage_notes': '', |
|
|
'statement': '' |
|
|
} |
|
|
|
|
|
|
|
|
if 'party_2' not in st.session_state: |
|
|
st.session_state.party_2 = { |
|
|
'full_name': '', |
|
|
'id_iqama': '', |
|
|
'phone': '', |
|
|
'role': 'Driver', |
|
|
'vehicle_make_model': '', |
|
|
'plate_number': '', |
|
|
'insurance': '', |
|
|
'damage_notes': '', |
|
|
'statement': '' |
|
|
} |
|
|
|
|
|
|
|
|
if 'evidence_photos' not in st.session_state: |
|
|
st.session_state.evidence_photos = [] |
|
|
|
|
|
|
|
|
if 'analysis_results' not in st.session_state: |
|
|
st.session_state.analysis_results = None |
|
|
|
|
|
|
|
|
if 'scenarios' not in st.session_state: |
|
|
st.session_state.scenarios = [] |
|
|
|
|
|
|
|
|
if 'map_data' not in st.session_state: |
|
|
st.session_state.map_data = None |
|
|
|
|
|
|
|
|
if 'analysis_ready' not in st.session_state: |
|
|
st.session_state.analysis_ready = False |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main(): |
|
|
"""Main application entry point.""" |
|
|
|
|
|
|
|
|
init_session_state() |
|
|
|
|
|
|
|
|
col1, col2, col3 = st.columns([1, 6, 1]) |
|
|
with col1: |
|
|
st.markdown("## π") |
|
|
with col2: |
|
|
st.markdown(""" |
|
|
<div style='padding-top: 0.5rem;'> |
|
|
<h1 style='margin: 0; font-size: 2.5rem; font-weight: 800; background: linear-gradient(135deg, #60a5fa 0%, #3b82f6 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent;'>CrashLens AI</h1> |
|
|
<p style='margin: 0.3rem 0 0 0; color: rgba(255, 255, 255, 0.7); font-size: 1rem;'>Traffic Accident Scenario Analysis β’ MindSpore-ready β’ PDF report</p> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
with col3: |
|
|
st.write("") |
|
|
|
|
|
st.markdown("<br>", unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
render_sidebar() |
|
|
|
|
|
|
|
|
steps = ["π Location", "π Vehicle 1", "π Vehicle 2", "π₯ Parties", "π Evidence", "π Analysis", "π Results"] |
|
|
cols = st.columns(len(steps)) |
|
|
|
|
|
for i, (col, step_name) in enumerate(zip(cols, steps), 1): |
|
|
with col: |
|
|
if i < st.session_state.current_step: |
|
|
|
|
|
st.markdown(f""" |
|
|
<div style='text-align: center; padding: 0.75rem 0.25rem; |
|
|
background: linear-gradient(135deg, #10b981 0%, #059669 100%); |
|
|
color: white; border-radius: 10px; font-size: 0.8rem; font-weight: 600; |
|
|
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);'> |
|
|
β {step_name.split()[-1]} |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
elif i == st.session_state.current_step: |
|
|
|
|
|
st.markdown(f""" |
|
|
<div style='text-align: center; padding: 0.75rem 0.25rem; |
|
|
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%); |
|
|
color: white; border-radius: 10px; font-size: 0.8rem; font-weight: 600; |
|
|
box-shadow: 0 6px 16px rgba(59, 130, 246, 0.4); |
|
|
border: 2px solid rgba(96, 165, 250, 0.5);'> |
|
|
{step_name} |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
else: |
|
|
|
|
|
st.markdown(f""" |
|
|
<div style='text-align: center; padding: 0.75rem 0.25rem; |
|
|
background: rgba(255, 255, 255, 0.05); |
|
|
color: rgba(255, 255, 255, 0.4); border-radius: 10px; font-size: 0.8rem; |
|
|
border: 1px solid rgba(255, 255, 255, 0.1);'> |
|
|
{step_name.split()[-1]} |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
st.markdown("<div style='margin: 1.5rem 0; height: 1px; background: linear-gradient(90deg, transparent, rgba(255,255,255,0.1), transparent);'></div>", unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
if st.session_state.current_step == 1: |
|
|
render_step_1_location() |
|
|
elif st.session_state.current_step == 2: |
|
|
render_step_2_vehicle1() |
|
|
elif st.session_state.current_step == 3: |
|
|
render_step_3_vehicle2() |
|
|
elif st.session_state.current_step == 4: |
|
|
render_step_4_parties() |
|
|
elif st.session_state.current_step == 5: |
|
|
render_step_5_evidence() |
|
|
elif st.session_state.current_step == 6: |
|
|
render_step_6_analysis() |
|
|
elif st.session_state.current_step == 7: |
|
|
render_step_7_results() |
|
|
|
|
|
|
|
|
render_footer() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def render_step_1_location(): |
|
|
"""Render the location selection step.""" |
|
|
|
|
|
st.header("π Step 1: Accident Location") |
|
|
|
|
|
st.markdown(""" |
|
|
<div class="info-card"> |
|
|
<h4>Accident Details</h4> |
|
|
<p>Enter the location and conditions where the accident occurred.</p> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
col1, col2 = st.columns([2, 1]) |
|
|
|
|
|
with col1: |
|
|
|
|
|
render_map_section() |
|
|
|
|
|
with col2: |
|
|
st.subheader("Location Details") |
|
|
|
|
|
|
|
|
location_name = st.text_input( |
|
|
"Location Name", |
|
|
value=st.session_state.accident_info['location']['name'] |
|
|
) |
|
|
|
|
|
|
|
|
lat = st.number_input( |
|
|
"Latitude", |
|
|
value=st.session_state.accident_info['location']['latitude'], |
|
|
format="%.6f", |
|
|
step=0.0001 |
|
|
) |
|
|
|
|
|
lon = st.number_input( |
|
|
"Longitude", |
|
|
value=st.session_state.accident_info['location']['longitude'], |
|
|
format="%.6f", |
|
|
step=0.0001 |
|
|
) |
|
|
|
|
|
|
|
|
road_types = [ |
|
|
'roundabout', 'crossroad', 't_junction', 'highway_merge', |
|
|
'parking', 'highway', 'urban_road', 'other' |
|
|
] |
|
|
road_type_labels = { |
|
|
'roundabout': 'Roundabout (Ψ―ΩΨ§Ψ±)', |
|
|
'crossroad': 'Crossroad (ΨͺΩΨ§Ψ·ΨΉ)', |
|
|
't_junction': 'T-Junction', |
|
|
'highway_merge': 'Highway Merge', |
|
|
'parking': 'Parking / Low Speed', |
|
|
'highway': 'Highway', |
|
|
'urban_road': 'Urban Road', |
|
|
'other': 'Other' |
|
|
} |
|
|
|
|
|
road_type = st.selectbox( |
|
|
"Road Type", |
|
|
options=road_types, |
|
|
format_func=lambda x: road_type_labels.get(x, x.title()), |
|
|
index=road_types.index(st.session_state.accident_info.get('road_type', 'roundabout')) |
|
|
) |
|
|
|
|
|
|
|
|
accident_date = st.date_input("Accident Date") |
|
|
accident_time = st.time_input("Accident Time") |
|
|
|
|
|
|
|
|
weather = st.selectbox( |
|
|
"Weather Conditions", |
|
|
options=['clear', 'cloudy', 'rainy', 'foggy', 'sandstorm'], |
|
|
format_func=lambda x: x.title() |
|
|
) |
|
|
|
|
|
|
|
|
road_condition = st.selectbox( |
|
|
"Road Condition", |
|
|
options=['dry', 'wet', 'sandy', 'oily'], |
|
|
format_func=lambda x: x.title() |
|
|
) |
|
|
|
|
|
|
|
|
notes = st.text_area( |
|
|
"Notes (optional)", |
|
|
value=st.session_state.accident_info.get('notes', ''), |
|
|
placeholder="Any extra context about the accident...", |
|
|
height=80 |
|
|
) |
|
|
|
|
|
|
|
|
st.session_state.accident_info.update({ |
|
|
'location': { |
|
|
'name': location_name, |
|
|
'latitude': lat, |
|
|
'longitude': lon, |
|
|
'radius_meters': 200 |
|
|
}, |
|
|
'road_type': road_type, |
|
|
'datetime': f"{accident_date} {accident_time}", |
|
|
'weather': weather, |
|
|
'road_condition': road_condition, |
|
|
'notes': notes |
|
|
}) |
|
|
|
|
|
|
|
|
col1, col2, col3 = st.columns([1, 2, 1]) |
|
|
with col3: |
|
|
if st.button("Next: Vehicle 1 β", type="primary"): |
|
|
st.session_state.current_step = 2 |
|
|
st.rerun() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def render_step_2_vehicle1(): |
|
|
"""Render Vehicle 1 input step.""" |
|
|
|
|
|
st.header("π Step 2: First Vehicle Information") |
|
|
|
|
|
st.markdown(""" |
|
|
<div class="info-card" style="border-color: #FF4B4B;"> |
|
|
<h4>Vehicle 1 (Red)</h4> |
|
|
<p>Enter the details of the first vehicle involved in the accident.</p> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
col1, col2 = st.columns([2, 1]) |
|
|
|
|
|
with col1: |
|
|
|
|
|
render_map_section(vehicle_id=1) |
|
|
|
|
|
with col2: |
|
|
render_vehicle_input(1) |
|
|
|
|
|
|
|
|
col1, col2, col3 = st.columns([1, 2, 1]) |
|
|
with col1: |
|
|
if st.button("β Back"): |
|
|
st.session_state.current_step = 1 |
|
|
st.rerun() |
|
|
with col3: |
|
|
if st.button("Next: Vehicle 2 β", type="primary"): |
|
|
st.session_state.current_step = 3 |
|
|
st.rerun() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def render_step_3_vehicle2(): |
|
|
"""Render Vehicle 2 input step.""" |
|
|
|
|
|
st.header("π Step 3: Second Vehicle Information") |
|
|
|
|
|
st.markdown(""" |
|
|
<div class="info-card" style="border-color: #4B7BFF;"> |
|
|
<h4>Vehicle 2 (Blue)</h4> |
|
|
<p>Enter the details of the second vehicle involved in the accident.</p> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
col1, col2 = st.columns([2, 1]) |
|
|
|
|
|
with col1: |
|
|
|
|
|
render_map_section(vehicle_id=2) |
|
|
|
|
|
with col2: |
|
|
render_vehicle_input(2) |
|
|
|
|
|
|
|
|
col1, col2, col3 = st.columns([1, 2, 1]) |
|
|
with col1: |
|
|
if st.button("β Back"): |
|
|
st.session_state.current_step = 2 |
|
|
st.rerun() |
|
|
with col3: |
|
|
if st.button("Analyze Accident β", type="primary"): |
|
|
st.session_state.current_step = 4 |
|
|
st.rerun() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def render_step_4_parties(): |
|
|
"""Render the parties (driver details) input step.""" |
|
|
|
|
|
st.header("π₯ Step 4: Parties Information") |
|
|
|
|
|
st.markdown(""" |
|
|
<div class="info-card"> |
|
|
<h4>Driver & Vehicle Details</h4> |
|
|
<p>Enter information about the parties involved in the accident. This information will be included in the official report.</p> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
|
|
|
with col1: |
|
|
render_party_input(1) |
|
|
|
|
|
with col2: |
|
|
render_party_input(2) |
|
|
|
|
|
|
|
|
st.markdown("---") |
|
|
col1, col2, col3 = st.columns([1, 2, 1]) |
|
|
with col1: |
|
|
if st.button("β Back to Vehicle 2"): |
|
|
st.session_state.current_step = 3 |
|
|
st.rerun() |
|
|
with col3: |
|
|
if st.button("Next: Evidence β", type="primary"): |
|
|
st.session_state.current_step = 5 |
|
|
st.rerun() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def render_step_5_evidence(): |
|
|
"""Render the evidence upload step.""" |
|
|
|
|
|
st.header("π· Step 5: Evidence (Optional)") |
|
|
|
|
|
st.markdown(""" |
|
|
<div class="info-card"> |
|
|
<h4>Upload Evidence Photos</h4> |
|
|
<p>Upload photos of the accident scene, vehicle damage, or any other relevant evidence. This is optional but recommended.</p> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
render_evidence_upload() |
|
|
|
|
|
|
|
|
st.markdown("---") |
|
|
st.subheader("π Data Summary") |
|
|
|
|
|
col1, col2, col3 = st.columns(3) |
|
|
|
|
|
with col1: |
|
|
st.markdown("**π Location**") |
|
|
st.write(f"{st.session_state.accident_info['location'].get('name', 'Not set')}") |
|
|
st.write(f"Type: {st.session_state.accident_info.get('road_type', 'N/A').replace('_', ' ').title()}") |
|
|
|
|
|
with col2: |
|
|
st.markdown("**π Vehicle 1**") |
|
|
|
|
|
v1 = st.session_state.vehicle_1 |
|
|
if isinstance(v1, dict): |
|
|
st.write(f"{v1.get('type', 'sedan').title()} @ {v1.get('speed', 50)} km/h") |
|
|
else: |
|
|
st.write("Vehicle 1 data") |
|
|
party1_name = st.session_state.party_1.get('full_name', '') if isinstance(st.session_state.party_1, dict) else '' |
|
|
if party1_name: |
|
|
st.write(f"Driver: {party1_name}") |
|
|
|
|
|
with col3: |
|
|
st.markdown("**π Vehicle 2**") |
|
|
|
|
|
v2 = st.session_state.vehicle_2 |
|
|
if isinstance(v2, dict): |
|
|
st.write(f"{v2.get('type', 'sedan').title()} @ {v2.get('speed', 50)} km/h") |
|
|
else: |
|
|
st.write("Vehicle 2 data") |
|
|
party2_name = st.session_state.party_2.get('full_name', '') if isinstance(st.session_state.party_2, dict) else '' |
|
|
if party2_name: |
|
|
st.write(f"Driver: {party2_name}") |
|
|
|
|
|
|
|
|
st.markdown("---") |
|
|
col1, col2, col3 = st.columns([1, 2, 1]) |
|
|
with col1: |
|
|
if st.button("β Back to Parties"): |
|
|
st.session_state.current_step = 4 |
|
|
st.rerun() |
|
|
with col3: |
|
|
if st.button("Run Analysis β", type="primary"): |
|
|
st.session_state.current_step = 6 |
|
|
st.rerun() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def render_step_6_analysis(): |
|
|
"""Render the AI analysis step.""" |
|
|
|
|
|
st.header("π€ Step 4: AI Analysis") |
|
|
|
|
|
st.markdown(""" |
|
|
<div class="info-card"> |
|
|
<h4>MindSpore AI Analysis</h4> |
|
|
<p>Review the accident data below and click "Run AI Analysis" to generate possible scenarios.</p> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
validation_passed = True |
|
|
validation_messages = [] |
|
|
|
|
|
|
|
|
if not st.session_state.vehicle_1.get('path'): |
|
|
validation_messages.append("β οΈ Vehicle 1 path not defined - will use direction-based estimation") |
|
|
if not st.session_state.vehicle_2.get('path'): |
|
|
validation_messages.append("β οΈ Vehicle 2 path not defined - will use direction-based estimation") |
|
|
|
|
|
|
|
|
st.subheader("π Input Data Summary") |
|
|
|
|
|
col1, col2, col3 = st.columns(3) |
|
|
|
|
|
with col1: |
|
|
st.markdown(""" |
|
|
<div style='background: rgba(59, 130, 246, 0.15); padding: 1rem; border-radius: 12px; border-left: 4px solid #3b82f6; box-shadow: 0 4px 12px rgba(0,0,0,0.2);'> |
|
|
<h4 style='margin:0; color:#60a5fa; font-weight: 700;'>π Location</h4> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
st.write(f"**{st.session_state.accident_info['location'].get('name', 'Unknown')}**") |
|
|
st.write(f"π£οΈ Type: `{st.session_state.accident_info['road_type']}`") |
|
|
st.write(f"π€οΈ Weather: `{st.session_state.accident_info['weather']}`") |
|
|
st.write(f"π€οΈ Road: `{st.session_state.accident_info['road_condition']}`") |
|
|
st.write(f"π
{st.session_state.accident_info.get('datetime', 'Not specified')}") |
|
|
|
|
|
with col2: |
|
|
st.markdown(""" |
|
|
<div style='background: rgba(255, 75, 75, 0.15); padding: 1rem; border-radius: 12px; border-left: 4px solid #FF4B4B; box-shadow: 0 4px 12px rgba(0,0,0,0.2);'> |
|
|
<h4 style='margin:0; color:#ff6b6b; font-weight: 700;'>π Vehicle 1 (Red)</h4> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
v1 = st.session_state.vehicle_1 if isinstance(st.session_state.vehicle_1, dict) else {'type': 'sedan', 'speed': 50, 'direction': 'north', 'action': 'going_straight', 'path': []} |
|
|
st.write(f"**Type:** {v1.get('type', 'sedan').title()}") |
|
|
st.write(f"**Speed:** {v1.get('speed', 50)} km/h") |
|
|
st.write(f"**Direction:** {v1.get('direction', 'north').title()}") |
|
|
st.write(f"**Action:** {v1.get('action', 'going_straight').replace('_', ' ').title()}") |
|
|
path_status = "β
Defined" if v1.get('path') else "β οΈ Not set" |
|
|
st.write(f"**Path:** {path_status}") |
|
|
|
|
|
with col3: |
|
|
st.markdown(""" |
|
|
<div style='background: rgba(75, 123, 255, 0.15); padding: 1rem; border-radius: 12px; border-left: 4px solid #4B7BFF; box-shadow: 0 4px 12px rgba(0,0,0,0.2);'> |
|
|
<h4 style='margin:0; color:#6b9bff; font-weight: 700;'>π Vehicle 2 (Blue)</h4> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
v2 = st.session_state.vehicle_2 if isinstance(st.session_state.vehicle_2, dict) else {'type': 'sedan', 'speed': 50, 'direction': 'east', 'action': 'going_straight', 'path': []} |
|
|
st.write(f"**Type:** {v2.get('type', 'sedan').title()}") |
|
|
st.write(f"**Speed:** {v2.get('speed', 50)} km/h") |
|
|
st.write(f"**Direction:** {v2.get('direction', 'east').title()}") |
|
|
st.write(f"**Action:** {v2.get('action', 'going_straight').replace('_', ' ').title()}") |
|
|
path_status = "β
Defined" if v2.get('path') else "β οΈ Not set" |
|
|
st.write(f"**Path:** {path_status}") |
|
|
|
|
|
|
|
|
if validation_messages: |
|
|
st.markdown("---") |
|
|
for msg in validation_messages: |
|
|
st.warning(msg) |
|
|
|
|
|
st.markdown("---") |
|
|
|
|
|
|
|
|
st.subheader("π Run Analysis") |
|
|
|
|
|
col1, col2, col3 = st.columns([1, 2, 1]) |
|
|
|
|
|
with col2: |
|
|
if st.button("π§ Run MindSpore AI Analysis", type="primary", use_container_width=True): |
|
|
|
|
|
|
|
|
progress_bar = st.progress(0) |
|
|
status_text = st.empty() |
|
|
|
|
|
try: |
|
|
|
|
|
status_text.text("Step 1/4: Validating input data...") |
|
|
progress_bar.progress(10) |
|
|
import time |
|
|
time.sleep(0.5) |
|
|
|
|
|
|
|
|
status_text.text("Step 2/4: Extracting features...") |
|
|
progress_bar.progress(30) |
|
|
time.sleep(0.5) |
|
|
|
|
|
|
|
|
status_text.text("Step 3/4: Running MindSpore AI model...") |
|
|
progress_bar.progress(50) |
|
|
|
|
|
|
|
|
from analysis.scenario_analyzer import analyze_accident |
|
|
|
|
|
results = analyze_accident( |
|
|
accident_info=st.session_state.accident_info, |
|
|
vehicle_1=st.session_state.vehicle_1, |
|
|
vehicle_2=st.session_state.vehicle_2 |
|
|
) |
|
|
|
|
|
progress_bar.progress(80) |
|
|
time.sleep(0.3) |
|
|
|
|
|
|
|
|
status_text.text("Step 4/4: Generating scenarios...") |
|
|
progress_bar.progress(100) |
|
|
|
|
|
|
|
|
st.session_state.analysis_results = results |
|
|
st.session_state.scenarios = results.get('scenarios', []) |
|
|
st.session_state.analysis_ready = True |
|
|
|
|
|
status_text.empty() |
|
|
progress_bar.empty() |
|
|
|
|
|
|
|
|
st.success(f""" |
|
|
β
**Analysis Complete!** |
|
|
|
|
|
- Generated **{len(st.session_state.scenarios)}** possible scenarios |
|
|
- Most likely: **{results.get('most_likely_scenario', {}).get('type', 'Unknown').replace('_', ' ').title()}** |
|
|
- Confidence: **{results.get('overall_collision_probability', 0)*100:.1f}%** |
|
|
""") |
|
|
|
|
|
except Exception as e: |
|
|
st.error(f"β Analysis failed: {str(e)}") |
|
|
progress_bar.empty() |
|
|
status_text.empty() |
|
|
|
|
|
|
|
|
if st.session_state.analysis_results: |
|
|
st.markdown("---") |
|
|
st.subheader("π Quick Results Preview") |
|
|
|
|
|
results = st.session_state.analysis_results |
|
|
scenarios = st.session_state.scenarios |
|
|
|
|
|
|
|
|
metric_cols = st.columns(4) |
|
|
|
|
|
with metric_cols[0]: |
|
|
st.metric( |
|
|
"Scenarios Generated", |
|
|
len(scenarios) |
|
|
) |
|
|
|
|
|
with metric_cols[1]: |
|
|
most_likely = results.get('most_likely_scenario', {}) |
|
|
st.metric( |
|
|
"Most Likely", |
|
|
f"#{most_likely.get('id', 1)}" |
|
|
) |
|
|
|
|
|
with metric_cols[2]: |
|
|
prob = results.get('overall_collision_probability', 0) * 100 |
|
|
st.metric( |
|
|
"Collision Certainty", |
|
|
f"{prob:.1f}%" |
|
|
) |
|
|
|
|
|
with metric_cols[3]: |
|
|
fault = results.get('preliminary_fault_assessment', {}) |
|
|
st.metric( |
|
|
"Primary Factor", |
|
|
fault.get('primary_factor', 'Unknown').replace('_', ' ').title()[:15] |
|
|
) |
|
|
|
|
|
|
|
|
st.write("**Top Scenarios:**") |
|
|
for i, scenario in enumerate(scenarios[:3], 1): |
|
|
prob_pct = scenario['probability'] * 100 |
|
|
st.write(f"{i}. **{scenario['accident_type'].replace('_', ' ').title()}** - {prob_pct:.1f}%") |
|
|
|
|
|
|
|
|
st.markdown("---") |
|
|
col1, col2, col3 = st.columns([1, 2, 1]) |
|
|
with col1: |
|
|
if st.button("β Back to Evidence"): |
|
|
st.session_state.current_step = 5 |
|
|
st.rerun() |
|
|
|
|
|
with col3: |
|
|
if st.session_state.analysis_results: |
|
|
if st.button("View Full Results β", type="primary"): |
|
|
st.session_state.current_step = 7 |
|
|
st.rerun() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def render_step_7_results(): |
|
|
"""Render the results step.""" |
|
|
|
|
|
st.header("π Step 7: Analysis Results") |
|
|
|
|
|
if not st.session_state.analysis_results: |
|
|
st.warning("No analysis results available. Please run the analysis first.") |
|
|
if st.button("β Go to Analysis"): |
|
|
st.session_state.current_step = 6 |
|
|
st.rerun() |
|
|
return |
|
|
|
|
|
|
|
|
render_results() |
|
|
|
|
|
|
|
|
col1, col2, col3 = st.columns([1, 2, 1]) |
|
|
with col1: |
|
|
if st.button("β Back to Analysis"): |
|
|
st.session_state.current_step = 6 |
|
|
st.rerun() |
|
|
|
|
|
with col3: |
|
|
if st.button("π Start New Analysis"): |
|
|
|
|
|
for key in list(st.session_state.keys()): |
|
|
del st.session_state[key] |
|
|
st.rerun() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
main() |
|
|
|