shrut27's picture
Upload folder using huggingface_hub
f5823da verified
Raw
History Blame Contribute Delete
21.8 kB
"""Crash Intelligence — AI-powered automotive crash material platform."""
from __future__ import annotations
from pathlib import Path
import pandas as pd
import plotly.graph_objects as go
import streamlit as st
from utils.calculations import (
available_families,
card_to_text,
family_summary,
generate_material_card,
rank_materials,
recommend_for_scenario,
scenario_kpi,
)
from utils.data_generator import (
COMPONENTS,
CRASH_SCENARIOS,
JOINING_METHODS,
SOLVERS,
generate_all,
)
from utils.visualizations import (
cost_sustain_bubble,
energy_intrusion_scatter,
family_bar,
kpi_gauge,
radar_materials,
scatter_crash_vs_weight,
scenario_heatmap,
stress_strain_curves,
top_recommendations_bar,
validation_error_hist,
validation_parity,
)
DATA_DIR = Path(__file__).resolve().parent / "data"
st.set_page_config(
page_title="Crash Intelligence Platform",
page_icon="🛡️",
layout="wide",
initial_sidebar_state="expanded",
)
st.markdown(
"""
<style>
@import url('https://fonts.googleapis.com/css2?family=Source+Sans+3:wght@400;600;700&family=IBM+Plex+Sans:wght@500;600&display=swap');
html, body, [class*="css"] {
font-family: 'Source Sans 3', 'Segoe UI', sans-serif;
color: #0f172a;
}
.block-container { padding-top: 1.2rem; padding-bottom: 2rem; max-width: 1400px; }
h1, h2, h3 { font-family: 'IBM Plex Sans', sans-serif !important; color: #0B3D2E !important; }
div[data-testid="stMetricValue"] { font-size: 1.6rem; color: #0B6E4F; }
div[data-testid="stMetricLabel"] { color: #334155; }
section[data-testid="stSidebar"] {
background: linear-gradient(180deg, #0B3D2E 0%, #1B4965 100%);
}
section[data-testid="stSidebar"] * { color: #f8fafc !important; }
section[data-testid="stSidebar"] .stSelectbox label,
section[data-testid="stSidebar"] .stMultiSelect label,
section[data-testid="stSidebar"] .stSlider label {
color: #e2e8f0 !important;
}
.hero {
background: linear-gradient(120deg, #0B3D2E 0%, #1B4965 55%, #5FA8D3 100%);
color: #ffffff;
padding: 1.4rem 1.6rem;
border-radius: 12px;
margin-bottom: 1rem;
}
.hero h1 { color: #ffffff !important; margin: 0 0 0.35rem 0; font-size: 1.9rem; }
.hero p { color: #e2e8f0; margin: 0; font-size: 1.02rem; }
.card-box {
background: #ffffff;
border: 1px solid #e2e8f0;
border-left: 4px solid #0B6E4F;
border-radius: 8px;
padding: 0.9rem 1rem;
margin-bottom: 0.6rem;
color: #0f172a;
}
.stTabs [data-baseweb="tab"] { color: #0f172a; font-weight: 600; }
.stDataFrame { color: #0f172a; }
</style>
""",
unsafe_allow_html=True,
)
@st.cache_data(show_spinner="Loading crash material datasets…")
def load_datasets() -> dict[str, pd.DataFrame]:
materials_path = DATA_DIR / "materials.csv"
if not materials_path.exists():
generate_all(DATA_DIR)
return {
"materials": pd.read_csv(DATA_DIR / "materials.csv"),
"stress_strain": pd.read_csv(DATA_DIR / "stress_strain.csv"),
"recommendations": pd.read_csv(DATA_DIR / "recommendations.csv"),
"validation": pd.read_csv(DATA_DIR / "validation.csv"),
}
def render_hero() -> None:
st.markdown(
"""
<div class="hero">
<h1>Crash Intelligence Platform</h1>
<p>
AI-powered material recommendation, prediction, and CAE card generation for
automotive crash-performance applications across metals, composites, polymers, foams, and adhesives.
</p>
</div>
""",
unsafe_allow_html=True,
)
def main() -> None:
data = load_datasets()
materials = data["materials"]
stress = data["stress_strain"]
recommendations = data["recommendations"]
validation = data["validation"]
render_hero()
families = available_families()
with st.sidebar:
st.markdown("### Filters & Targets")
selected_families = st.multiselect(
"Material families",
options=families,
default=families[:8],
)
scenario = st.selectbox("Crash scenario", CRASH_SCENARIOS, index=0)
component = st.selectbox("Vehicle component", COMPONENTS, index=8)
max_cost = st.slider("Max cost (USD/kg)", 1.0, 80.0, 40.0, 1.0)
min_uts = st.slider("Min UTS (MPa)", 20, 1800, 200, 20)
max_density = st.slider("Max density (g/cm³)", 0.1, 8.0, 8.0, 0.1)
st.markdown("---")
st.markdown("### Multi-objective weights")
w_crash = st.slider("Crash performance", 0.0, 1.0, 0.30, 0.05)
w_weight = st.slider("Lightweighting", 0.0, 1.0, 0.20, 0.05)
w_cost = st.slider("Cost performance", 0.0, 1.0, 0.20, 0.05)
w_sust = st.slider("Sustainability", 0.0, 1.0, 0.15, 0.05)
w_fail = st.slider("Low failure risk", 0.0, 1.0, 0.15, 0.05)
weights = {
"crash": w_crash,
"weight": w_weight,
"cost": w_cost,
"sustainability": w_sust,
"failure": w_fail,
}
st.markdown("---")
st.caption(
f"Database: {len(materials):,} materials · "
f"{len(recommendations):,} scenario predictions · "
f"{len(validation):,} validation pairs"
)
filt = materials[materials["family"].isin(selected_families)].copy()
if filt.empty:
st.warning("No materials match the selected families. Expand the family filter.")
return
filt = filt[
(filt["cost_usd_kg"] <= max_cost)
& (filt["uts_mpa"] >= min_uts)
& (filt["density_g_cm3"] <= max_density)
]
if filt.empty:
st.warning("No materials match the current property filters. Relax cost / UTS / density limits.")
return
rec_filt = recommendations[recommendations["family"].isin(selected_families)]
ranked = rank_materials(
filt,
families=selected_families,
max_cost=max_cost,
min_uts=min_uts,
max_density=max_density,
weights=weights,
top_n=8,
)
tabs = st.tabs(
[
"Overview",
"Material Explorer",
"Crash Scenario AI",
"Compare & Rank",
"Material Cards",
"Validation",
"Data Library",
]
)
# --- Overview ---
with tabs[0]:
st.subheader("Platform KPIs")
c1, c2, c3, c4, c5 = st.columns(5)
c1.metric("Materials", f"{len(filt):,}")
c2.metric("Avg Crash Index", f"{filt['crashworthiness_index'].mean():.1f}")
c3.metric("Avg Energy Potential", f"{filt['energy_absorption_potential'].mean():.2f}")
c4.metric("Avg Sustainability", f"{filt['sustainability_score'].mean():.1f}")
c5.metric("AI–CAE Pass Rate", f"{(validation['pass_fail']=='Pass').mean()*100:.0f}%")
g1, g2, g3 = st.columns(3)
with g1:
st.plotly_chart(
kpi_gauge(float(filt["crashworthiness_index"].mean()), "Crashworthiness", "#0B6E4F"),
use_container_width=True,
)
with g2:
st.plotly_chart(
kpi_gauge(float(filt["lightweighting_score"].mean()), "Lightweighting", "#1B4965"),
use_container_width=True,
)
with g3:
st.plotly_chart(
kpi_gauge(float(filt["sustainability_score"].mean()), "Sustainability", "#2A9D8F"),
use_container_width=True,
)
st.markdown("#### Family performance & trade-offs")
summary = family_summary(filt)
r1, r2 = st.columns(2)
with r1:
st.plotly_chart(
family_bar(summary, "crashworthiness_index", "Crashworthiness by Family"),
use_container_width=True,
)
with r2:
st.plotly_chart(scatter_crash_vs_weight(filt), use_container_width=True)
st.plotly_chart(scenario_heatmap(rec_filt), use_container_width=True)
st.markdown(
"""
<div class="card-box">
<strong>Value proposition:</strong> Shortlist safer, lighter, cheaper, and more sustainable
crash-critical materials before expensive CAE and physical testing — then export draft
solver-ready material cards for LS-DYNA, Abaqus, PAM-CRASH, and Radioss.
</div>
""",
unsafe_allow_html=True,
)
# --- Material Explorer ---
with tabs[1]:
st.subheader("Material Data Explorer")
st.markdown(
"Browse standardized mechanical, cost, and sustainability properties across automotive crash materials."
)
m1, m2 = st.columns(2)
with m1:
st.plotly_chart(
family_bar(summary, "energy_absorption_potential", "Energy Absorption Potential"),
use_container_width=True,
)
with m2:
st.plotly_chart(cost_sustain_bubble(filt), use_container_width=True)
curve_options = (
stress[stress["family"].isin(selected_families)][["material_id", "material_name", "family"]]
.drop_duplicates()
.head(80)
)
if not curve_options.empty:
pick = st.multiselect(
"Select materials for stress–strain curves",
options=curve_options["material_id"].tolist(),
default=curve_options["material_id"].tolist()[:3],
format_func=lambda mid: (
f"{curve_options.loc[curve_options.material_id==mid, 'material_name'].iloc[0]} "
f"({curve_options.loc[curve_options.material_id==mid, 'family'].iloc[0]})"
),
)
if pick:
st.plotly_chart(stress_strain_curves(stress, pick), use_container_width=True)
st.dataframe(
filt[
[
"material_name",
"family",
"density_g_cm3",
"youngs_modulus_gpa",
"yield_strength_mpa",
"uts_mpa",
"elongation_pct",
"failure_strain",
"cost_usd_kg",
"co2_kg_kg",
"crashworthiness_index",
"sustainability_score",
"source",
"confidence_score",
]
].sort_values("crashworthiness_index", ascending=False),
use_container_width=True,
height=360,
)
# --- Crash Scenario AI ---
with tabs[2]:
st.subheader("Crash Scenario Intelligence")
st.markdown(
f"Recommendations for **{scenario}** on **{component}** using multi-objective AI ranking."
)
top_rec = recommend_for_scenario(
filt,
rec_filt,
scenario=scenario,
component=component,
families=selected_families,
top_n=5,
)
if top_rec.empty:
st.info("No recommendations available for this combination.")
else:
k1, k2, k3, k4 = st.columns(4)
k1.metric("Top Crash Score", f"{top_rec['crash_score'].iloc[0]:.1f}")
k2.metric(
"Best Weight Reduction",
f"{top_rec.get('weight_reduction_pct', pd.Series([0])).iloc[0]:.1f}%",
)
k3.metric("Top Material", str(top_rec["material_name"].iloc[0]))
k4.metric("Family", str(top_rec["family"].iloc[0]))
st.plotly_chart(top_recommendations_bar(top_rec), use_container_width=True)
c_a, c_b = st.columns(2)
with c_a:
st.plotly_chart(energy_intrusion_scatter(rec_filt), use_container_width=True)
with c_b:
sk = scenario_kpi(rec_filt)
st.plotly_chart(
family_bar(
sk.rename(columns={"crash_scenario": "family", "avg_crash_score": "crashworthiness_index"}),
"crashworthiness_index",
"Average Crash Score by Scenario",
),
use_container_width=True,
)
st.markdown("#### Top 5 recommendations")
display_cols = [
c
for c in [
"material_name",
"family",
"thickness_mm",
"joining_method",
"crash_score",
"energy_absorption_kj",
"intrusion_mm",
"peak_force_kn",
"crush_force_efficiency",
"weight_reduction_pct",
"cost_score",
"sustainability_score",
"simulation_risk",
]
if c in top_rec.columns
]
st.dataframe(top_rec[display_cols], use_container_width=True)
st.markdown("#### Suggested next steps")
best = top_rec.iloc[0]
join = best.get("joining_method", JOINING_METHODS[0])
thick = best.get("thickness_mm", 2.0)
st.markdown(
f"""
<div class="card-box">
<strong>Recommended action:</strong> Evaluate <em>{best['material_name']}</em>
({best['family']}) at ~{thick} mm with <em>{join}</em> joining.
Expected crash score {best['crash_score']:.1f}.
Run component-level {scenario.lower()} CAE before physical validation.
</div>
""",
unsafe_allow_html=True,
)
# --- Compare & Rank ---
with tabs[3]:
st.subheader("Material Comparison & Ranking")
if ranked.empty:
st.info("No ranked materials under current constraints.")
else:
st.plotly_chart(radar_materials(ranked), use_container_width=True)
left, right = st.columns(2)
with left:
st.plotly_chart(
family_bar(
ranked.rename(columns={"material_name": "family", "mo_score": "crashworthiness_index"})[
["family", "crashworthiness_index"]
],
"crashworthiness_index",
"Multi-Objective Score (Top Materials)",
),
use_container_width=True,
)
with right:
st.dataframe(
ranked[
[
"material_name",
"family",
"mo_score",
"crashworthiness_index",
"lightweighting_score",
"cost_performance_score",
"sustainability_score",
"failure_risk",
"uts_mpa",
"density_g_cm3",
"cost_usd_kg",
]
],
use_container_width=True,
height=420,
)
# --- Material Cards ---
with tabs[4]:
st.subheader("CAE Material Card Generator")
st.markdown(
"Generate draft solver-ready material cards including elastic modulus, yield, "
"plastic curve, strain-rate sensitivity, failure strain, and confidence score."
)
card_mat_name = st.selectbox(
"Select material",
options=ranked["material_name"].tolist() if not ranked.empty else filt["material_name"].head(50).tolist(),
)
solver = st.selectbox("Target solver", SOLVERS)
mat_row = filt[filt["material_name"] == card_mat_name]
if mat_row.empty and not ranked.empty:
mat_row = ranked[ranked["material_name"] == card_mat_name]
if mat_row.empty:
mat_row = materials[materials["material_name"] == card_mat_name]
if not mat_row.empty:
material = mat_row.iloc[0]
card = generate_material_card(material, solver=solver)
text = card_to_text(card)
mc1, mc2, mc3, mc4 = st.columns(4)
mc1.metric("Card Type", card["card_type"].split()[0])
mc2.metric("Yield (MPa)", f"{card['yield_strength_mpa']:.0f}")
mc3.metric("Failure Strain", f"{card['failure_strain']:.3f}")
mc4.metric("Confidence", f"{card['confidence_score']:.2f}")
col_l, col_r = st.columns([1.1, 0.9])
with col_l:
st.code(text, language="text")
st.download_button(
"Download material card",
data=text,
file_name=f"{material['material_name']}_{solver.replace(' ', '_')}.k",
mime="text/plain",
)
with col_r:
curve_id = material["material_id"] if "material_id" in material.index else None
if curve_id and curve_id in stress["material_id"].values:
st.plotly_chart(
stress_strain_curves(stress, [curve_id]),
use_container_width=True,
)
else:
fig = go.Figure()
fig.add_trace(
go.Scatter(
x=card["plastic_curve_strain"],
y=card["plastic_curve_stress_mpa"],
mode="lines+markers",
line=dict(color="#0B6E4F", width=3),
name="Plastic curve",
)
)
fig.update_layout(
title="Draft Plastic Curve",
xaxis_title="Plastic Strain",
yaxis_title="Stress (MPa)",
height=400,
paper_bgcolor="white",
plot_bgcolor="#f8fafc",
font=dict(color="#1a1a1a"),
)
st.plotly_chart(fig, use_container_width=True)
st.markdown(
f"""
<div class="card-box">
<strong>Validation status:</strong> {card['validation_status']}<br/>
<strong>Damage model:</strong> {card['damage_evolution']}<br/>
<strong>Temperature:</strong> {card['temperature_dependency']}
</div>
""",
unsafe_allow_html=True,
)
# --- Validation ---
with tabs[5]:
st.subheader("Validation Workflow")
st.markdown(
"Compare AI predictions with CAE and physical crash proxies aligned to Euro NCAP / FMVSS / IIHS."
)
val = validation[validation["family"].isin(selected_families)]
v1, v2, v3, v4 = st.columns(4)
v1.metric("Validation pairs", f"{len(val):,}")
v2.metric("Mean AI–CAE error", f"{val['ai_cae_error_pct'].mean():.1f}%")
v3.metric("Pass rate", f"{(val['pass_fail']=='Pass').mean()*100:.0f}%")
v4.metric("Mean NHTSA-star proxy", f"{val['nhtsa_star_proxy'].mean():.1f}")
vc1, vc2 = st.columns(2)
with vc1:
st.plotly_chart(validation_parity(val), use_container_width=True)
with vc2:
st.plotly_chart(validation_error_hist(val), use_container_width=True)
st.markdown("#### Validation ladder")
levels = [
("Coupon tests", "Tensile, compression, shear, strain-rate, fracture"),
("Component tests", "Bumper beam, crash box, rail, door beam, battery enclosure"),
("CAE validation", "Compare AI prediction with LS-DYNA / Abaqus / PAM-CRASH"),
("Physical crash", "Compare simulation with crash-test measurements"),
("Certification", "Euro NCAP, FMVSS, IIHS, OEM internal standards"),
]
for title, desc in levels:
st.markdown(
f'<div class="card-box"><strong>{title}:</strong> {desc}</div>',
unsafe_allow_html=True,
)
st.dataframe(
val.sort_values("ai_cae_error_pct").head(200),
use_container_width=True,
height=320,
)
# --- Data Library ---
with tabs[6]:
st.subheader("Data Library & Export")
st.markdown(
"Public-style material and crash datasets used by the ranking and card-generation engines."
)
dataset_choice = st.selectbox(
"Dataset",
["materials", "recommendations", "validation", "stress_strain"],
)
export_df = data[dataset_choice]
if dataset_choice != "stress_strain":
if "family" in export_df.columns:
export_df = export_df[export_df["family"].isin(selected_families)]
st.dataframe(export_df.head(500), use_container_width=True, height=400)
st.download_button(
f"Download {dataset_choice}.csv",
data=export_df.to_csv(index=False),
file_name=f"{dataset_choice}.csv",
mime="text/csv",
)
st.markdown("---")
st.caption(
"Crash Intelligence Platform · Prototype powered by public-style material & crash databases · "
"For OEM production use, calibrate with supplier cards, high strain-rate tests, and full-vehicle CAE."
)
if __name__ == "__main__":
main()