"""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( """ """, 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( """

Crash Intelligence Platform

AI-powered material recommendation, prediction, and CAE card generation for automotive crash-performance applications across metals, composites, polymers, foams, and adhesives.

""", 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( """
Value proposition: 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.
""", 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"""
Recommended action: Evaluate {best['material_name']} ({best['family']}) at ~{thick} mm with {join} joining. Expected crash score {best['crash_score']:.1f}. Run component-level {scenario.lower()} CAE before physical validation.
""", 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"""
Validation status: {card['validation_status']}
Damage model: {card['damage_evolution']}
Temperature: {card['temperature_dependency']}
""", 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'
{title}: {desc}
', 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()