Download index.html from WellmatixGenAI/Crash-Intelligence: direct link, hf CLI and curl.
- Browser
- Download file 42.8 kB
-
https://huggingface.co/spaces/WellmatixGenAI/Crash-Intelligence/resolve/main/index.html
- Command line
-
hf download hf://spaces/WellmatixGenAI/Crash-Intelligence/index.html
-
curl -L -o index.html https://huggingface.co/spaces/WellmatixGenAI/Crash-Intelligence/resolve/main/index.html
42.8 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
| <title>Crash Intelligence Platform</title> | |
| <script src="https://cdn.plot.ly/plotly-2.35.2.min.js"></script> | |
| <link rel="preconnect" href="https://fonts.googleapis.com" /> | |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> | |
| <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@500;600;700&family=Source+Sans+3:wght@400;600;700&display=swap" rel="stylesheet" /> | |
| <style> | |
| :root { | |
| --ink: #0f172a; | |
| --muted: #475569; | |
| --green: #0b6e4f; | |
| --green-dark: #0b3d2e; | |
| --blue: #1b4965; | |
| --sky: #5fa8d3; | |
| --bg: #f1f5f9; | |
| --card: #ffffff; | |
| --line: #e2e8f0; | |
| --danger: #c44536; | |
| } | |
| * { box-sizing: border-box; } | |
| html, body { | |
| margin: 0; | |
| padding: 0; | |
| background: var(--bg); | |
| color: var(--ink); | |
| font-family: "Source Sans 3", "Segoe UI", sans-serif; | |
| } | |
| .layout { | |
| display: grid; | |
| grid-template-columns: 280px 1fr; | |
| min-height: 100vh; | |
| } | |
| .sidebar { | |
| background: linear-gradient(180deg, #0b3d2e 0%, #1b4965 100%); | |
| color: #f8fafc; | |
| padding: 1.25rem 1rem 2rem; | |
| position: sticky; | |
| top: 0; | |
| height: 100vh; | |
| overflow-y: auto; | |
| } | |
| .sidebar h2 { | |
| font-family: "IBM Plex Sans", sans-serif; | |
| font-size: 1.05rem; | |
| margin: 0 0 0.75rem; | |
| color: #fff; | |
| } | |
| .sidebar label { | |
| display: block; | |
| font-size: 0.82rem; | |
| margin: 0.75rem 0 0.3rem; | |
| color: #e2e8f0; | |
| } | |
| .sidebar select, | |
| .sidebar input[type="range"] { | |
| width: 100%; | |
| } | |
| .sidebar select { | |
| background: rgba(255,255,255,0.12); | |
| color: #fff; | |
| border: 1px solid rgba(255,255,255,0.25); | |
| border-radius: 8px; | |
| padding: 0.45rem 0.5rem; | |
| } | |
| .sidebar select option { color: #0f172a; } | |
| .family-box { | |
| max-height: 180px; | |
| overflow-y: auto; | |
| background: rgba(255,255,255,0.08); | |
| border-radius: 8px; | |
| padding: 0.4rem 0.55rem; | |
| border: 1px solid rgba(255,255,255,0.15); | |
| } | |
| .family-box label { | |
| display: flex; | |
| gap: 0.4rem; | |
| align-items: center; | |
| margin: 0.25rem 0; | |
| font-size: 0.78rem; | |
| color: #f1f5f9; | |
| } | |
| .weight-val { float: right; color: #bae6fd; } | |
| .caption { | |
| margin-top: 1rem; | |
| font-size: 0.75rem; | |
| color: #cbd5e1; | |
| line-height: 1.4; | |
| } | |
| .main { padding: 1.1rem 1.4rem 2rem; } | |
| .hero { | |
| background: linear-gradient(120deg, #0b3d2e 0%, #1b4965 55%, #5fa8d3 100%); | |
| color: #fff; | |
| border-radius: 14px; | |
| padding: 1.35rem 1.5rem; | |
| margin-bottom: 1rem; | |
| } | |
| .hero h1 { | |
| font-family: "IBM Plex Sans", sans-serif; | |
| margin: 0 0 0.35rem; | |
| font-size: 1.85rem; | |
| color: #fff; | |
| } | |
| .hero p { margin: 0; color: #e2e8f0; max-width: 900px; line-height: 1.45; } | |
| .tabs { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 0.4rem; | |
| margin: 0.8rem 0 1rem; | |
| } | |
| .tab-btn { | |
| background: #fff; | |
| border: 1px solid var(--line); | |
| color: var(--ink); | |
| border-radius: 999px; | |
| padding: 0.45rem 0.9rem; | |
| font-weight: 600; | |
| cursor: pointer; | |
| font-size: 0.9rem; | |
| } | |
| .tab-btn.active { | |
| background: var(--green); | |
| border-color: var(--green); | |
| color: #fff; | |
| } | |
| .panel { display: none; } | |
| .panel.active { display: block; } | |
| .metrics { | |
| display: grid; | |
| grid-template-columns: repeat(5, minmax(0, 1fr)); | |
| gap: 0.75rem; | |
| margin-bottom: 1rem; | |
| } | |
| .metric { | |
| background: var(--card); | |
| border: 1px solid var(--line); | |
| border-left: 4px solid var(--green); | |
| border-radius: 10px; | |
| padding: 0.85rem 1rem; | |
| } | |
| .metric .label { color: var(--muted); font-size: 0.82rem; } | |
| .metric .value { | |
| color: var(--green); | |
| font-size: 1.45rem; | |
| font-weight: 700; | |
| margin-top: 0.15rem; | |
| font-family: "IBM Plex Sans", sans-serif; | |
| } | |
| .grid-2 { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 0.9rem; | |
| margin-bottom: 0.9rem; | |
| } | |
| .grid-3 { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr 1fr; | |
| gap: 0.9rem; | |
| margin-bottom: 0.9rem; | |
| } | |
| .card { | |
| background: var(--card); | |
| border: 1px solid var(--line); | |
| border-radius: 12px; | |
| padding: 0.75rem; | |
| min-height: 120px; | |
| } | |
| .card h3 { | |
| margin: 0 0 0.5rem; | |
| font-size: 1rem; | |
| color: var(--green-dark); | |
| font-family: "IBM Plex Sans", sans-serif; | |
| } | |
| .note { | |
| background: #fff; | |
| border: 1px solid var(--line); | |
| border-left: 4px solid var(--green); | |
| border-radius: 10px; | |
| padding: 0.9rem 1rem; | |
| color: var(--ink); | |
| margin: 0.8rem 0; | |
| line-height: 1.45; | |
| } | |
| table.data { | |
| width: 100%; | |
| border-collapse: collapse; | |
| font-size: 0.85rem; | |
| background: #fff; | |
| } | |
| table.data th, table.data td { | |
| border-bottom: 1px solid var(--line); | |
| padding: 0.45rem 0.5rem; | |
| text-align: left; | |
| color: var(--ink); | |
| } | |
| table.data th { | |
| background: #ecfdf5; | |
| color: var(--green-dark); | |
| position: sticky; | |
| top: 0; | |
| } | |
| .table-wrap { | |
| max-height: 360px; | |
| overflow: auto; | |
| border: 1px solid var(--line); | |
| border-radius: 10px; | |
| background: #fff; | |
| } | |
| .controls-row { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 0.75rem; | |
| align-items: end; | |
| margin-bottom: 0.8rem; | |
| } | |
| .controls-row label { | |
| font-size: 0.82rem; | |
| color: var(--muted); | |
| display: block; | |
| margin-bottom: 0.25rem; | |
| } | |
| .controls-row select, .controls-row button, .controls-row input { | |
| padding: 0.45rem 0.6rem; | |
| border-radius: 8px; | |
| border: 1px solid var(--line); | |
| background: #fff; | |
| color: var(--ink); | |
| } | |
| .controls-row button { | |
| background: var(--green); | |
| color: #fff; | |
| border: none; | |
| font-weight: 600; | |
| cursor: pointer; | |
| } | |
| pre.card-text { | |
| background: #0f172a; | |
| color: #e2e8f0; | |
| padding: 1rem; | |
| border-radius: 10px; | |
| overflow: auto; | |
| font-size: 0.82rem; | |
| line-height: 1.4; | |
| min-height: 320px; | |
| } | |
| .loading { | |
| min-height: 60vh; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| color: var(--green-dark); | |
| font-size: 1.1rem; | |
| } | |
| .footer { | |
| margin-top: 1.2rem; | |
| color: var(--muted); | |
| font-size: 0.8rem; | |
| border-top: 1px solid var(--line); | |
| padding-top: 0.8rem; | |
| } | |
| @media (max-width: 1100px) { | |
| .layout { grid-template-columns: 1fr; } | |
| .sidebar { position: relative; height: auto; } | |
| .metrics { grid-template-columns: repeat(2, 1fr); } | |
| .grid-2, .grid-3 { grid-template-columns: 1fr; } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="app" class="loading">Loading Crash Intelligence comprehensive dashboard…</div> | |
| <script> | |
| const COLORS = ["#0B6E4F","#08A045","#1B4965","#5FA8D3","#C44536","#E8871E","#6B4C9A","#2A9D8F","#E76F51","#264653","#F4A261","#457B9D","#9B2226","#005F73","#CA6702"]; | |
| const LAYOUT = { | |
| paper_bgcolor: "#ffffff", | |
| plot_bgcolor: "#f8fafc", | |
| font: { family: "Source Sans 3, Segoe UI, sans-serif", color: "#0f172a", size: 12 }, | |
| margin: { l: 55, r: 25, t: 45, b: 50 }, | |
| legend: { bgcolor: "rgba(255,255,255,0.92)", bordercolor: "#ddd", borderwidth: 1 } | |
| }; | |
| const PLOT_CONFIG = { | |
| responsive: true, | |
| displayModeBar: true, | |
| displaylogo: false, | |
| scrollZoom: true, | |
| staticPlot: false, | |
| modeBarButtonsToRemove: ["lasso2d", "select2d"] | |
| }; | |
| let DATA = null; | |
| let state = { | |
| families: [], | |
| scenario: "Side Impact", | |
| component: "Battery Enclosure", | |
| maxCost: 40, | |
| minUts: 200, | |
| maxDensity: 8, | |
| weights: { crash: 0.3, weight: 0.2, cost: 0.2, sustain: 0.15, failure: 0.15 }, | |
| tab: "overview" | |
| }; | |
| function num(v, d=1) { return Number(v).toFixed(d); } | |
| function pct(v) { return `${(100 * v).toFixed(0)}%`; } | |
| function filteredTable() { | |
| return DATA.table.filter(r => | |
| state.families.includes(r.family) && | |
| r.cost_usd_kg <= state.maxCost && | |
| r.uts_mpa >= state.minUts && | |
| r.density_g_cm3 <= state.maxDensity | |
| ); | |
| } | |
| function filteredScatter() { | |
| return DATA.scatter.filter(r => | |
| state.families.includes(r.family) && | |
| r.cost_usd_kg <= state.maxCost && | |
| r.uts_mpa >= state.minUts && | |
| r.density_g_cm3 <= state.maxDensity | |
| ); | |
| } | |
| function familySummary() { | |
| return DATA.family_summary.filter(r => state.families.includes(r.family)); | |
| } | |
| function moScore(r) { | |
| const w = state.weights; | |
| const tot = w.crash + w.weight + w.cost + w.sustain + w.failure || 1; | |
| return ( | |
| (w.crash / tot) * r.crashworthiness_index + | |
| (w.weight / tot) * r.lightweighting_score + | |
| (w.cost / tot) * Math.min(r.cost_performance_score * 1.2, 100) + | |
| (w.sustain / tot) * r.sustainability_score + | |
| (w.failure / tot) * (100 * (1 - r.failure_risk)) | |
| ); | |
| } | |
| function rankedMaterials(n = 8) { | |
| return [...filteredTable()] | |
| .map(r => ({ ...r, mo_score: moScore(r) })) | |
| .sort((a, b) => b.mo_score - a.mo_score) | |
| .slice(0, n); | |
| } | |
| function scenarioRecs() { | |
| const key = `${state.scenario}||${state.component}`; | |
| let rows = (DATA.recommendations[key] || []).filter(r => state.families.includes(r.family)); | |
| if (!rows.length) { | |
| rows = rankedMaterials(5).map(r => ({ | |
| material_name: r.material_name, | |
| family: r.family, | |
| joining_method: "Hybrid Weld-Bond", | |
| crash_score: r.crashworthiness_index, | |
| energy_absorption_kj: r.energy_absorption_potential * 2, | |
| intrusion_mm: 40, | |
| peak_force_kn: r.uts_mpa * 0.03, | |
| crush_force_efficiency: 0.65, | |
| weight_reduction_pct: r.lightweighting_score * 0.4, | |
| cost_score: r.cost_performance_score, | |
| sustainability_score: r.sustainability_score, | |
| simulation_risk: r.failure_risk * 100, | |
| thickness_mm: 2.0 | |
| })); | |
| } | |
| return rows.slice(0, 5); | |
| } | |
| function plotBarH(div, rows, xKey, yKey, title, colorScale = false) { | |
| const sorted = [...rows].sort((a, b) => a[xKey] - b[xKey]); | |
| const trace = { | |
| type: "bar", | |
| orientation: "h", | |
| x: sorted.map(r => r[xKey]), | |
| y: sorted.map(r => r[yKey]), | |
| marker: colorScale | |
| ? { color: sorted.map(r => r[xKey]), colorscale: [[0, "#D8F3DC"], [1, "#0B6E4F"]], showscale: false } | |
| : { color: "#0B6E4F" }, | |
| hovertemplate: `%{y}<br>${xKey}: %{x:.2f}<extra></extra>` | |
| }; | |
| Plotly.newPlot(div, [trace], { ...LAYOUT, title, height: 430 }, PLOT_CONFIG); | |
| } | |
| function plotScatter(div, rows, x, y, color, size, title) { | |
| const families = [...new Set(rows.map(r => r[color]))]; | |
| const traces = families.map((f, i) => { | |
| const sub = rows.filter(r => r[color] === f); | |
| return { | |
| type: "scatter", | |
| mode: "markers", | |
| name: f, | |
| x: sub.map(r => r[x]), | |
| y: sub.map(r => r[y]), | |
| text: sub.map(r => r.material_name || ""), | |
| marker: { | |
| size: sub.map(r => size ? Math.max(7, Math.min(22, (r[size] || 10) / 80)) : 9), | |
| color: COLORS[i % COLORS.length], | |
| opacity: 0.75, | |
| line: { width: 0.5, color: "#fff" } | |
| }, | |
| hovertemplate: "%{text}<br>%{xaxis.title.text}: %{x:.1f}<br>%{yaxis.title.text}: %{y:.1f}<extra>" + f + "</extra>" | |
| }; | |
| }); | |
| Plotly.newPlot(div, traces, { | |
| ...LAYOUT, | |
| title, | |
| height: 430, | |
| xaxis: { title: x.replaceAll("_", " ") }, | |
| yaxis: { title: y.replaceAll("_", " ") } | |
| }, PLOT_CONFIG); | |
| } | |
| function plotRadar(div, rows) { | |
| const cats = ["Crash", "Weight", "Cost-Perf", "Sustainability", "Energy Abs."]; | |
| const traces = rows.slice(0, 5).map((r, i) => { | |
| const vals = [ | |
| r.crashworthiness_index, | |
| r.lightweighting_score, | |
| Math.min(r.cost_performance_score * 1.5, 100), | |
| r.sustainability_score, | |
| Math.min(r.energy_absorption_potential * 2, 100) | |
| ]; | |
| vals.push(vals[0]); | |
| return { | |
| type: "scatterpolar", | |
| r: vals, | |
| theta: [...cats, cats[0]], | |
| fill: "toself", | |
| name: r.material_name, | |
| opacity: 0.55, | |
| line: { color: COLORS[i % COLORS.length], width: 2 } | |
| }; | |
| }); | |
| Plotly.newPlot(div, traces, { | |
| ...LAYOUT, | |
| title: "Multi-Objective Material Comparison", | |
| height: 460, | |
| polar: { | |
| bgcolor: "#f8fafc", | |
| radialaxis: { visible: true, range: [0, 100], gridcolor: "#e5e7eb" }, | |
| angularaxis: { gridcolor: "#e5e7eb" } | |
| } | |
| }, PLOT_CONFIG); | |
| } | |
| function plotHeatmap(div) { | |
| const rows = DATA.heatmap.filter(r => state.families.includes(r.family)); | |
| const scenarios = [...new Set(rows.map(r => r.crash_scenario))]; | |
| const families = [...new Set(rows.map(r => r.family))]; | |
| const z = scenarios.map(s => families.map(f => { | |
| const hit = rows.find(r => r.crash_scenario === s && r.family === f); | |
| return hit ? hit.crash_score : null; | |
| })); | |
| Plotly.newPlot(div, [{ | |
| type: "heatmap", | |
| z, x: families, y: scenarios, | |
| colorscale: [[0, "#F1FAEE"], [0.5, "#1B4965"], [1, "#0B6E4F"]], | |
| hoverongaps: false, | |
| colorbar: { title: "Crash Score" } | |
| }], { ...LAYOUT, title: "Avg Crash Score by Scenario × Family", height: 520 }, PLOT_CONFIG); | |
| } | |
| function plotGauge(div, value, title, color) { | |
| Plotly.newPlot(div, [{ | |
| type: "indicator", | |
| mode: "gauge+number", | |
| value, | |
| title: { text: title, font: { size: 14, color: "#1a1a1a" } }, | |
| number: { font: { color: "#1a1a1a" } }, | |
| gauge: { | |
| axis: { range: [0, 100] }, | |
| bar: { color }, | |
| bgcolor: "#f1f5f9", | |
| bordercolor: "#cbd5e1", | |
| steps: [ | |
| { range: [0, 40], color: "#fee2e2" }, | |
| { range: [40, 70], color: "#fef3c7" }, | |
| { range: [70, 100], color: "#dcfce7" } | |
| ] | |
| } | |
| }], { height: 220, margin: { t: 40, b: 10, l: 20, r: 20 }, paper_bgcolor: "#fff", font: { color: "#1a1a1a" } }, PLOT_CONFIG); | |
| } | |
| function plotCurves(div, ids) { | |
| const traces = []; | |
| ids.forEach((id, i) => { | |
| const c = DATA.curves[id]; | |
| if (!c) return; | |
| traces.push({ | |
| type: "scatter", mode: "lines", name: `${c.material_name} (QS)`, | |
| x: c.strain, y: c.stress_mpa, | |
| line: { color: COLORS[i % COLORS.length], width: 2.5 } | |
| }); | |
| traces.push({ | |
| type: "scatter", mode: "lines", name: `${c.material_name} (HR)`, | |
| x: c.strain, y: c.stress_high_rate_mpa, | |
| line: { color: COLORS[i % COLORS.length], width: 2, dash: "dash" } | |
| }); | |
| }); | |
| Plotly.newPlot(div, traces, { | |
| ...LAYOUT, | |
| title: "Stress–Strain Curves (Strain-Rate Sensitive)", | |
| height: 440, | |
| xaxis: { title: "True Strain" }, | |
| yaxis: { title: "True Stress (MPa)" } | |
| }, PLOT_CONFIG); | |
| } | |
| function plotValidation(divParity, divHist) { | |
| const rows = DATA.validation.filter(r => state.families.includes(r.family)); | |
| Plotly.newPlot(divParity, [ | |
| { | |
| type: "scatter", mode: "markers", name: "AI vs CAE", | |
| x: rows.map(r => r.cae_crash_score), | |
| y: rows.map(r => r.ai_crash_score), | |
| marker: { color: "#1B4965", size: 7, opacity: 0.55 } | |
| }, | |
| { | |
| type: "scatter", mode: "lines", name: "Ideal", | |
| x: [0, 100], y: [0, 100], | |
| line: { color: "#C44536", dash: "dash", width: 2 } | |
| } | |
| ], { ...LAYOUT, title: "AI Prediction vs CAE Validation", height: 420, xaxis: { title: "CAE Crash Score" }, yaxis: { title: "AI Crash Score" } }, PLOT_CONFIG); | |
| const pass = rows.filter(r => r.pass_fail === "Pass").map(r => r.ai_cae_error_pct); | |
| const review = rows.filter(r => r.pass_fail !== "Pass").map(r => r.ai_cae_error_pct); | |
| Plotly.newPlot(divHist, [ | |
| { type: "histogram", x: pass, name: "Pass", marker: { color: "#0B6E4F" }, opacity: 0.85 }, | |
| { type: "histogram", x: review, name: "Review", marker: { color: "#C44536" }, opacity: 0.85 } | |
| ], { ...LAYOUT, barmode: "overlay", title: "AI–CAE Error Distribution", height: 420, xaxis: { title: "AI–CAE Error (%)" } }, PLOT_CONFIG); | |
| } | |
| function materialCard(mat, solver) { | |
| const n = 10; | |
| const strains = Array.from({ length: n }, (_, i) => i * mat.failure_strain / (n - 1)); | |
| const stresses = strains.map((eps, i) => { | |
| const t = i / (n - 1); | |
| return mat.yield_strength_mpa + (mat.uts_mpa - mat.yield_strength_mpa) * t; | |
| }); | |
| const cardType = mat.material_card_type || "MAT_024"; | |
| const pr = (mat.family || "").includes("Aluminum") || mat.family === "Magnesium" ? 0.30 : 0.29; | |
| let text = `*KEYWORD (${solver} draft)\n`; | |
| text += `$ Material: ${mat.material_name} (${mat.family})\n`; | |
| text += `$ Card type: ${cardType}\n`; | |
| text += `$ Confidence: ${num(mat.confidence_score, 2)}\n`; | |
| text += `$ Validation: Draft — public-data prototype\n`; | |
| text += `*MAT_PIECEWISE_LINEAR_PLASTICITY\n`; | |
| text += `$ RO (g/cm3) = ${mat.density_g_cm3}\n`; | |
| text += `$ E (GPa) = ${mat.youngs_modulus_gpa}\n`; | |
| text += `$ PR = ${pr}\n`; | |
| text += `$ SIGY (MPa) = ${mat.yield_strength_mpa}\n`; | |
| text += `$ FAIL = ${mat.failure_strain}\n`; | |
| text += `$ C (strain-rate) = ${mat.strain_rate_sensitivity}\n`; | |
| text += `$ Plastic curve (strain, stress MPa):\n`; | |
| strains.forEach((e, i) => { text += `$ ${e.toFixed(5)}, ${stresses[i].toFixed(2)}\n`; }); | |
| text += `$ Damage: Linear softening to zero stress at failure strain\n`; | |
| text += `$ Temperature: Room-temperature card; scale factors TBD\n*END`; | |
| return { text, strains, stresses, cardType }; | |
| } | |
| function tableHTML(rows, cols) { | |
| if (!rows.length) return "<p>No rows match the current filters.</p>"; | |
| const head = cols.map(c => `<th>${c.label}</th>`).join(""); | |
| const body = rows.map(r => `<tr>${cols.map(c => `<td>${typeof r[c.key] === "number" ? num(r[c.key], c.digits ?? 2) : (r[c.key] ?? "")}</td>`).join("")}</tr>`).join(""); | |
| return `<div class="table-wrap"><table class="data"><thead><tr>${head}</tr></thead><tbody>${body}</tbody></table></div>`; | |
| } | |
| function renderShell() { | |
| const families = DATA.meta.families; | |
| if (!state.families.length) state.families = families.slice(); | |
| document.getElementById("app").className = ""; | |
| document.getElementById("app").innerHTML = ` | |
| <div class="layout"> | |
| <aside class="sidebar"> | |
| <h2>Filters & Targets</h2> | |
| <label>Material families</label> | |
| <div class="family-box" id="familyBox"></div> | |
| <label>Crash scenario</label> | |
| <select id="scenario"></select> | |
| <label>Vehicle component</label> | |
| <select id="component"></select> | |
| <label>Max cost (USD/kg): <span id="maxCostVal">${state.maxCost}</span></label> | |
| <input type="range" id="maxCost" min="1" max="80" step="1" value="${state.maxCost}" /> | |
| <label>Min UTS (MPa): <span id="minUtsVal">${state.minUts}</span></label> | |
| <input type="range" id="minUts" min="20" max="1800" step="20" value="${state.minUts}" /> | |
| <label>Max density (g/cm³): <span id="maxDensityVal">${state.maxDensity}</span></label> | |
| <input type="range" id="maxDensity" min="0.1" max="8" step="0.1" value="${state.maxDensity}" /> | |
| <h2 style="margin-top:1.2rem">Multi-objective weights</h2> | |
| <label>Crash <span class="weight-val" id="wCrashVal">${state.weights.crash.toFixed(2)}</span></label> | |
| <input type="range" id="wCrash" min="0" max="1" step="0.05" value="${state.weights.crash}" /> | |
| <label>Lightweighting <span class="weight-val" id="wWeightVal">${state.weights.weight.toFixed(2)}</span></label> | |
| <input type="range" id="wWeight" min="0" max="1" step="0.05" value="${state.weights.weight}" /> | |
| <label>Cost <span class="weight-val" id="wCostVal">${state.weights.cost.toFixed(2)}</span></label> | |
| <input type="range" id="wCost" min="0" max="1" step="0.05" value="${state.weights.cost}" /> | |
| <label>Sustainability <span class="weight-val" id="wSustVal">${state.weights.sustain.toFixed(2)}</span></label> | |
| <input type="range" id="wSust" min="0" max="1" step="0.05" value="${state.weights.sustain}" /> | |
| <label>Low failure risk <span class="weight-val" id="wFailVal">${state.weights.failure.toFixed(2)}</span></label> | |
| <input type="range" id="wFail" min="0" max="1" step="0.05" value="${state.weights.failure}" /> | |
| <p class="caption" id="dbCaption"></p> | |
| </aside> | |
| <main class="main"> | |
| <div class="hero"> | |
| <h1>Crash Intelligence Platform</h1> | |
| <p>AI-powered material recommendation, prediction, and CAE card generation for automotive crash-performance across steel, aluminum, magnesium, composites, polymers, foams, elastomers, and adhesives.</p> | |
| </div> | |
| <div class="tabs" id="tabs"> | |
| <button class="tab-btn" data-tab="overview">Overview</button> | |
| <button class="tab-btn" data-tab="explorer">Material Explorer</button> | |
| <button class="tab-btn" data-tab="scenario">Crash Scenario AI</button> | |
| <button class="tab-btn" data-tab="compare">Compare & Rank</button> | |
| <button class="tab-btn" data-tab="cards">Material Cards</button> | |
| <button class="tab-btn" data-tab="validation">Validation</button> | |
| <button class="tab-btn" data-tab="library">Data Library</button> | |
| </div> | |
| <div id="content"></div> | |
| <div class="footer">Crash Intelligence Platform · Public-style material & crash databases · Calibrate with supplier cards and full-vehicle CAE for OEM production use.</div> | |
| </main> | |
| </div>`; | |
| const famBox = document.getElementById("familyBox"); | |
| famBox.innerHTML = families.map(f => ` | |
| <label><input type="checkbox" value="${f}" ${state.families.includes(f) ? "checked" : ""}/> ${f}</label> | |
| `).join(""); | |
| const scen = document.getElementById("scenario"); | |
| scen.innerHTML = DATA.meta.scenarios.map(s => `<option ${s === state.scenario ? "selected" : ""}>${s}</option>`).join(""); | |
| const comp = document.getElementById("component"); | |
| comp.innerHTML = DATA.meta.components.map(s => `<option ${s === state.component ? "selected" : ""}>${s}</option>`).join(""); | |
| document.getElementById("dbCaption").textContent = | |
| `Database: ${DATA.meta.n_materials.toLocaleString()} materials · ${DATA.meta.n_recommendations.toLocaleString()} scenario predictions · ${DATA.meta.n_validation.toLocaleString()} validation pairs`; | |
| bindControls(); | |
| setTab(state.tab); | |
| } | |
| function bindControls() { | |
| document.querySelectorAll("#familyBox input").forEach(cb => { | |
| cb.addEventListener("change", () => { | |
| state.families = [...document.querySelectorAll("#familyBox input:checked")].map(x => x.value); | |
| if (!state.families.length) state.families = DATA.meta.families.slice(); | |
| renderActive(); | |
| }); | |
| }); | |
| const map = [ | |
| ["scenario", v => state.scenario = v], | |
| ["component", v => state.component = v], | |
| ]; | |
| map.forEach(([id, fn]) => document.getElementById(id).addEventListener("change", e => { fn(e.target.value); renderActive(); })); | |
| const ranges = [ | |
| ["maxCost", v => { state.maxCost = +v; document.getElementById("maxCostVal").textContent = v; }], | |
| ["minUts", v => { state.minUts = +v; document.getElementById("minUtsVal").textContent = v; }], | |
| ["maxDensity", v => { state.maxDensity = +v; document.getElementById("maxDensityVal").textContent = v; }], | |
| ["wCrash", v => { state.weights.crash = +v; document.getElementById("wCrashVal").textContent = (+v).toFixed(2); }], | |
| ["wWeight", v => { state.weights.weight = +v; document.getElementById("wWeightVal").textContent = (+v).toFixed(2); }], | |
| ["wCost", v => { state.weights.cost = +v; document.getElementById("wCostVal").textContent = (+v).toFixed(2); }], | |
| ["wSust", v => { state.weights.sustain = +v; document.getElementById("wSustVal").textContent = (+v).toFixed(2); }], | |
| ["wFail", v => { state.weights.failure = +v; document.getElementById("wFailVal").textContent = (+v).toFixed(2); }], | |
| ]; | |
| ranges.forEach(([id, fn]) => { | |
| const el = document.getElementById(id); | |
| el.addEventListener("input", e => fn(e.target.value)); | |
| el.addEventListener("change", () => renderActive()); | |
| }); | |
| document.querySelectorAll(".tab-btn").forEach(btn => { | |
| btn.addEventListener("click", () => setTab(btn.dataset.tab)); | |
| }); | |
| } | |
| function setTab(tab) { | |
| state.tab = tab; | |
| document.querySelectorAll(".tab-btn").forEach(b => b.classList.toggle("active", b.dataset.tab === tab)); | |
| renderActive(); | |
| } | |
| function renderActive() { | |
| const root = document.getElementById("content"); | |
| const filt = filteredTable(); | |
| const avg = (key) => filt.length ? filt.reduce((s, r) => s + r[key], 0) / filt.length : 0; | |
| if (state.tab === "overview") { | |
| root.innerHTML = ` | |
| <div class="metrics"> | |
| <div class="metric"><div class="label">Materials</div><div class="value">${filt.length.toLocaleString()}</div></div> | |
| <div class="metric"><div class="label">Avg Crash Index</div><div class="value">${num(avg("crashworthiness_index"))}</div></div> | |
| <div class="metric"><div class="label">Avg Energy Potential</div><div class="value">${num(avg("energy_absorption_potential"), 2)}</div></div> | |
| <div class="metric"><div class="label">Avg Sustainability</div><div class="value">${num(avg("sustainability_score"))}</div></div> | |
| <div class="metric"><div class="label">AI–CAE Pass Rate</div><div class="value">${pct(DATA.meta.pass_rate)}</div></div> | |
| </div> | |
| <div class="grid-3"> | |
| <div class="card"><div id="g1"></div></div> | |
| <div class="card"><div id="g2"></div></div> | |
| <div class="card"><div id="g3"></div></div> | |
| </div> | |
| <div class="grid-2"> | |
| <div class="card"><div id="p1"></div></div> | |
| <div class="card"><div id="p2"></div></div> | |
| </div> | |
| <div class="card"><div id="p3"></div></div> | |
| <div class="note"><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> | |
| `; | |
| plotGauge("g1", avg("crashworthiness_index"), "Crashworthiness", "#0B6E4F"); | |
| plotGauge("g2", avg("lightweighting_score"), "Lightweighting", "#1B4965"); | |
| plotGauge("g3", avg("sustainability_score"), "Sustainability", "#2A9D8F"); | |
| plotBarH("p1", familySummary(), "crash", "family", "Crashworthiness by Family", true); | |
| plotScatter("p2", filteredScatter(), "lightweighting_score", "crashworthiness_index", "family", "uts_mpa", "Crashworthiness vs Lightweighting"); | |
| plotHeatmap("p3"); | |
| return; | |
| } | |
| if (state.tab === "explorer") { | |
| const curveIds = Object.keys(DATA.curves).filter(id => state.families.includes(DATA.curves[id].family)).slice(0, 4); | |
| root.innerHTML = ` | |
| <h3 style="color:var(--green-dark);font-family:IBM Plex Sans,sans-serif">Material Data Explorer</h3> | |
| <p style="color:var(--muted)">Browse standardized mechanical, cost, and sustainability properties across automotive crash materials.</p> | |
| <div class="grid-2"> | |
| <div class="card"><div id="e1"></div></div> | |
| <div class="card"><div id="e2"></div></div> | |
| </div> | |
| <div class="card"><div id="e3"></div></div> | |
| ${tableHTML(filt.slice(0, 120), [ | |
| {key:"material_name", label:"Material"}, | |
| {key:"family", label:"Family"}, | |
| {key:"density_g_cm3", label:"Density"}, | |
| {key:"youngs_modulus_gpa", label:"E (GPa)"}, | |
| {key:"yield_strength_mpa", label:"Yield"}, | |
| {key:"uts_mpa", label:"UTS"}, | |
| {key:"elongation_pct", label:"Elong %"}, | |
| {key:"cost_usd_kg", label:"Cost"}, | |
| {key:"crashworthiness_index", label:"Crash"}, | |
| {key:"sustainability_score", label:"Sustain"}, | |
| {key:"source", label:"Source"}, | |
| {key:"confidence_score", label:"Conf", digits:2} | |
| ])} | |
| `; | |
| plotBarH("e1", familySummary(), "energy", "family", "Energy Absorption Potential", true); | |
| plotScatter("e2", filteredScatter(), "cost_usd_kg", "sustainability_score", "family", "crashworthiness_index", "Cost vs Sustainability"); | |
| plotCurves("e3", curveIds); | |
| return; | |
| } | |
| if (state.tab === "scenario") { | |
| const top = scenarioRecs(); | |
| const best = top[0] || {}; | |
| root.innerHTML = ` | |
| <h3 style="color:var(--green-dark);font-family:IBM Plex Sans,sans-serif">Crash Scenario Intelligence</h3> | |
| <p style="color:var(--muted)">Recommendations for <strong>${state.scenario}</strong> on <strong>${state.component}</strong>.</p> | |
| <div class="metrics"> | |
| <div class="metric"><div class="label">Top Crash Score</div><div class="value">${num(best.crash_score || 0)}</div></div> | |
| <div class="metric"><div class="label">Best Weight Reduction</div><div class="value">${num(best.weight_reduction_pct || 0)}%</div></div> | |
| <div class="metric"><div class="label">Top Material</div><div class="value" style="font-size:1.05rem">${best.material_name || "—"}</div></div> | |
| <div class="metric"><div class="label">Family</div><div class="value" style="font-size:1.05rem">${best.family || "—"}</div></div> | |
| <div class="metric"><div class="label">Joining</div><div class="value" style="font-size:0.95rem">${best.joining_method || "—"}</div></div> | |
| </div> | |
| <div class="grid-2"> | |
| <div class="card"><div id="s1"></div></div> | |
| <div class="card"><div id="s2"></div></div> | |
| </div> | |
| <div class="card"><div id="s3"></div></div> | |
| ${tableHTML(top, [ | |
| {key:"material_name", label:"Material"}, | |
| {key:"family", label:"Family"}, | |
| {key:"thickness_mm", label:"Thickness"}, | |
| {key:"joining_method", label:"Joining"}, | |
| {key:"crash_score", label:"Crash"}, | |
| {key:"energy_absorption_kj", label:"Energy"}, | |
| {key:"intrusion_mm", label:"Intrusion"}, | |
| {key:"weight_reduction_pct", label:"ΔWeight %"}, | |
| {key:"simulation_risk", label:"Sim Risk"} | |
| ])} | |
| <div class="note"><strong>Recommended action:</strong> Evaluate <em>${best.material_name || "candidate"}</em> | |
| (${best.family || ""}) at ~${num(best.thickness_mm || 2, 2)} mm with <em>${best.joining_method || "hybrid joining"}</em>. | |
| Expected crash score ${num(best.crash_score || 0)}. Run component-level ${state.scenario.toLowerCase()} CAE before physical validation.</div> | |
| `; | |
| plotBarH("s1", top.map(r => ({ family: r.material_name, crash: r.crash_score })), "crash", "family", "Top Recommended Materials", true); | |
| plotScatter("s2", DATA.energy_intrusion.filter(r => state.families.includes(r.family)), "intrusion_mm", "energy_absorption_kj", "crash_scenario", null, "Energy Absorption vs Intrusion"); | |
| plotBarH("s3", DATA.scenario_kpi.map(r => ({ family: r.crash_scenario, crash: r.avg_crash_score })), "crash", "family", "Average Crash Score by Scenario", true); | |
| return; | |
| } | |
| if (state.tab === "compare") { | |
| const ranked = rankedMaterials(8); | |
| root.innerHTML = ` | |
| <h3 style="color:var(--green-dark);font-family:IBM Plex Sans,sans-serif">Material Comparison & Ranking</h3> | |
| <div class="grid-2"> | |
| <div class="card"><div id="c1"></div></div> | |
| <div class="card"><div id="c2"></div></div> | |
| </div> | |
| ${tableHTML(ranked, [ | |
| {key:"material_name", label:"Material"}, | |
| {key:"family", label:"Family"}, | |
| {key:"mo_score", label:"MO Score"}, | |
| {key:"crashworthiness_index", label:"Crash"}, | |
| {key:"lightweighting_score", label:"Weight"}, | |
| {key:"cost_performance_score", label:"Cost-Perf"}, | |
| {key:"sustainability_score", label:"Sustain"}, | |
| {key:"failure_risk", label:"Fail Risk", digits:3}, | |
| {key:"uts_mpa", label:"UTS"}, | |
| {key:"density_g_cm3", label:"Density"}, | |
| {key:"cost_usd_kg", label:"Cost"} | |
| ])} | |
| `; | |
| plotRadar("c1", ranked); | |
| plotBarH("c2", ranked.map(r => ({ family: r.material_name, crash: r.mo_score })), "crash", "family", "Multi-Objective Score (Top Materials)", true); | |
| return; | |
| } | |
| if (state.tab === "cards") { | |
| const ranked = rankedMaterials(20); | |
| const options = ranked.length ? ranked : filt.slice(0, 20); | |
| root.innerHTML = ` | |
| <h3 style="color:var(--green-dark);font-family:IBM Plex Sans,sans-serif">CAE Material Card Generator</h3> | |
| <p style="color:var(--muted)">Generate draft solver-ready material cards with elastic modulus, yield, plastic curve, strain-rate sensitivity, failure strain, and confidence score.</p> | |
| <div class="controls-row"> | |
| <div><label>Select material</label><select id="cardMat">${options.map(r => `<option value="${r.material_name}">${r.material_name} (${r.family})</option>`).join("")}</select></div> | |
| <div><label>Target solver</label><select id="cardSolver">${DATA.solvers.map(s => `<option>${s}</option>`).join("")}</select></div> | |
| <div><label> </label><button id="dlCard">Download card</button></div> | |
| </div> | |
| <div class="metrics" id="cardMetrics"></div> | |
| <div class="grid-2"> | |
| <pre class="card-text" id="cardText"></pre> | |
| <div class="card"><div id="cardPlot"></div><div class="note" id="cardNote"></div></div> | |
| </div> | |
| `; | |
| const updateCard = () => { | |
| const name = document.getElementById("cardMat").value; | |
| const solver = document.getElementById("cardSolver").value; | |
| const mat = options.find(r => r.material_name === name) || options[0]; | |
| if (!mat) return; | |
| const card = materialCard(mat, solver); | |
| document.getElementById("cardText").textContent = card.text; | |
| document.getElementById("cardMetrics").innerHTML = ` | |
| <div class="metric"><div class="label">Card Type</div><div class="value" style="font-size:1rem">${card.cardType.split(" ")[0]}</div></div> | |
| <div class="metric"><div class="label">Yield (MPa)</div><div class="value">${num(mat.yield_strength_mpa, 0)}</div></div> | |
| <div class="metric"><div class="label">Failure Strain</div><div class="value">${num(mat.failure_strain, 3)}</div></div> | |
| <div class="metric"><div class="label">Confidence</div><div class="value">${num(mat.confidence_score, 2)}</div></div> | |
| <div class="metric"><div class="label">UTS (MPa)</div><div class="value">${num(mat.uts_mpa, 0)}</div></div>`; | |
| document.getElementById("cardNote").innerHTML = `<strong>Validation status:</strong> Draft — public-data prototype<br/><strong>Damage model:</strong> Linear softening to zero stress at failure strain<br/><strong>Temperature:</strong> Room-temperature card; scale factors TBD`; | |
| if (DATA.curves[mat.material_id]) { | |
| plotCurves("cardPlot", [mat.material_id]); | |
| } else { | |
| Plotly.newPlot("cardPlot", [{ | |
| type: "scatter", mode: "lines+markers", | |
| x: card.strains, y: card.stresses, | |
| line: { color: "#0B6E4F", width: 3 }, name: "Plastic curve" | |
| }], { ...LAYOUT, title: "Draft Plastic Curve", height: 380, xaxis: { title: "Plastic Strain" }, yaxis: { title: "Stress (MPa)" } }, PLOT_CONFIG); | |
| } | |
| document.getElementById("dlCard").onclick = () => { | |
| const blob = new Blob([card.text], { type: "text/plain" }); | |
| const a = document.createElement("a"); | |
| a.href = URL.createObjectURL(blob); | |
| a.download = `${mat.material_name}_${solver.replaceAll(" ", "_")}.k`; | |
| a.click(); | |
| }; | |
| }; | |
| document.getElementById("cardMat").addEventListener("change", updateCard); | |
| document.getElementById("cardSolver").addEventListener("change", updateCard); | |
| updateCard(); | |
| return; | |
| } | |
| if (state.tab === "validation") { | |
| const rows = DATA.validation.filter(r => state.families.includes(r.family)); | |
| const meanErr = rows.length ? rows.reduce((s, r) => s + r.ai_cae_error_pct, 0) / rows.length : 0; | |
| const passRate = rows.length ? rows.filter(r => r.pass_fail === "Pass").length / rows.length : 0; | |
| const meanStar = rows.length ? rows.reduce((s, r) => s + r.nhtsa_star_proxy, 0) / rows.length : 0; | |
| root.innerHTML = ` | |
| <h3 style="color:var(--green-dark);font-family:IBM Plex Sans,sans-serif">Validation Workflow</h3> | |
| <p style="color:var(--muted)">Compare AI predictions with CAE and physical crash proxies aligned to Euro NCAP / FMVSS / IIHS.</p> | |
| <div class="metrics"> | |
| <div class="metric"><div class="label">Validation pairs</div><div class="value">${rows.length.toLocaleString()}</div></div> | |
| <div class="metric"><div class="label">Mean AI–CAE error</div><div class="value">${num(meanErr)}%</div></div> | |
| <div class="metric"><div class="label">Pass rate</div><div class="value">${pct(passRate)}</div></div> | |
| <div class="metric"><div class="label">Mean NHTSA-star proxy</div><div class="value">${num(meanStar)}</div></div> | |
| <div class="metric"><div class="label">Standards</div><div class="value" style="font-size:0.95rem">NCAP/FMVSS</div></div> | |
| </div> | |
| <div class="grid-2"> | |
| <div class="card"><div id="v1"></div></div> | |
| <div class="card"><div id="v2"></div></div> | |
| </div> | |
| <div class="note"><strong>Coupon tests:</strong> Tensile, compression, shear, strain-rate, fracture.</div> | |
| <div class="note"><strong>Component tests:</strong> Bumper beam, crash box, rail, door beam, battery enclosure.</div> | |
| <div class="note"><strong>CAE validation:</strong> Compare AI prediction with LS-DYNA / Abaqus / PAM-CRASH.</div> | |
| <div class="note"><strong>Physical crash:</strong> Compare simulation with crash-test measurements.</div> | |
| <div class="note"><strong>Certification:</strong> Euro NCAP, FMVSS, IIHS, OEM internal standards.</div> | |
| ${tableHTML(rows.slice(0, 150), [ | |
| {key:"family", label:"Family"}, | |
| {key:"crash_scenario", label:"Scenario"}, | |
| {key:"ai_crash_score", label:"AI"}, | |
| {key:"cae_crash_score", label:"CAE"}, | |
| {key:"physical_crash_score", label:"Physical"}, | |
| {key:"ai_cae_error_pct", label:"Error %"}, | |
| {key:"pass_fail", label:"Status"}, | |
| {key:"nhtsa_star_proxy", label:"Stars", digits:0}, | |
| {key:"standard", label:"Standard"} | |
| ])} | |
| `; | |
| plotValidation("v1", "v2"); | |
| return; | |
| } | |
| // library | |
| root.innerHTML = ` | |
| <h3 style="color:var(--green-dark);font-family:IBM Plex Sans,sans-serif">Data Library & Export</h3> | |
| <p style="color:var(--muted)">Public-style material and crash datasets used by the ranking and card-generation engines.</p> | |
| <div class="controls-row"> | |
| <div><label>Dataset</label> | |
| <select id="libSet"> | |
| <option value="table">materials</option> | |
| <option value="validation">validation</option> | |
| <option value="top_materials">top_materials</option> | |
| <option value="scenario_kpi">scenario_kpi</option> | |
| </select> | |
| </div> | |
| <div><label> </label><button id="dlCsv">Download CSV</button></div> | |
| </div> | |
| <div id="libTable"></div> | |
| `; | |
| const renderLib = () => { | |
| const key = document.getElementById("libSet").value; | |
| let rows = DATA[key]; | |
| if (key === "table" || key === "validation") rows = rows.filter(r => !r.family || state.families.includes(r.family)); | |
| const cols = Object.keys(rows[0] || {}).slice(0, 12).map(k => ({ key: k, label: k, digits: 3 })); | |
| document.getElementById("libTable").innerHTML = tableHTML(rows.slice(0, 250), cols); | |
| document.getElementById("dlCsv").onclick = () => { | |
| const header = Object.keys(rows[0] || {}); | |
| const csv = [header.join(",")].concat(rows.map(r => header.map(h => JSON.stringify(r[h] ?? "")).join(","))).join("\n"); | |
| const a = document.createElement("a"); | |
| a.href = URL.createObjectURL(new Blob([csv], { type: "text/csv" })); | |
| a.download = `${key}.csv`; | |
| a.click(); | |
| }; | |
| }; | |
| document.getElementById("libSet").addEventListener("change", renderLib); | |
| renderLib(); | |
| } | |
| fetch("./data/dashboard_payload.json") | |
| .then(r => { | |
| if (!r.ok) throw new Error("Failed to load dashboard payload"); | |
| return r.json(); | |
| }) | |
| .then(json => { | |
| DATA = json; | |
| state.families = json.meta.families.slice(); | |
| state.scenario = json.meta.scenarios.includes("Side Impact") ? "Side Impact" : json.meta.scenarios[0]; | |
| state.component = json.meta.components.includes("Battery Enclosure") ? "Battery Enclosure" : json.meta.components[0]; | |
| renderShell(); | |
| }) | |
| .catch(err => { | |
| document.getElementById("app").innerHTML = `<div class="loading" style="color:#C44536">Failed to load dashboard: ${err.message}</div>`; | |
| }); | |
| </script> | |
| </body> | |
| </html> | |