import streamlit as st import pandas as pd import numpy as np import plotly.graph_objects as go import os # Configuration de la page st.set_page_config(page_title="Brake Performance Lab", layout="wide", page_icon="🚲") # --- LE "SUPER CSS" POUR FORCER LE BLANC ET LE NOIR --- st.markdown(""" """, unsafe_allow_html=True) @st.cache_data def load_data(): current_dir = os.path.dirname(__file__) file_path = os.path.join(current_dir, "Brake_Lab_Test_Data.xlsx") data = pd.read_excel(file_path, sheet_name='Data') data.columns = data.columns.str.strip() return data try: df = load_data() all_models = df['model name'].unique().tolist() with st.sidebar: st.image("https://upload.wikimedia.org/wikipedia/commons/thumb/0/08/Decathlon_Logo.svg/1280px-Decathlon_Logo.svg.png", width=200) st.title("⚙️ SETTINGS") x_input = st.slider("🫱 Lever Effort [N]", 40, 200, 100) st.markdown("---") selected_models = st.multiselect("Select Models to Display", options=all_models, default=all_models[:2]) st.markdown("---") st.subheader("📋 Standard Compliance") norm_type = st.selectbox("Category", ["None", "City/Trekking", "Kids", "MTB", "Racing"]) n_dry, n_wet = 0, 0 if norm_type == "City/Trekking": n_dry, n_wet = 340, 220 elif norm_type == "Kids": n_dry, n_wet = 204, 132 elif norm_type == "MTB": n_dry, n_wet = 425, 280 elif norm_type == "Racing": n_dry, n_wet = 425, 260 with st.expander("🔍 Display Options"): show_loss = st.checkbox("Show Wet Loss Analysis", value=True) enable_comparison = st.checkbox("Enable Reference Comparison", value=True) ref_model = st.selectbox("Reference Model (Benchmark)", options=all_models) condition_view = st.radio("Conditions to display", ["Both", "Dry only", "Wet only"], index=0) # --- DIAGNOSTIC HEADER --- if x_input < 70: label, color_alert = "❄️ LIGHT BRAKING", "#a1c4fd" elif 70 <= x_input <= 110: label, color_alert = "⚖️ MODERATE BRAKING", "#ffdb58" else: label, color_alert = "🔥 POWERFUL BRAKING", "#ff4b4b" st.markdown(f"""
{label} | Effort: {round(float(x_input), 1)} N
""", unsafe_allow_html=True) # --- GRAPHIC AREA --- filtered_df = df[df['model name'].isin(selected_models)] fig = go.Figure() x_range = np.linspace(40, 200, 150) # Couleurs courbes : Bleu Decathlon, Rouge, Noir, Vert, Jaune colors = ['#0082C3', '#E63312', '#000000', '#00A14B', '#FFD200'] row_ref = df[df['model name'] == ref_model].iloc[0] ref_dry_val = row_ref['dry a'] * x_input + row_ref['dry b'] ref_wet_val = row_ref['wet a'] * x_input + row_ref['wet b'] comparison_results = [] for i, (index, row) in enumerate(filtered_df.iterrows()): color = colors[i % len(colors)] y_d = row['dry a'] * x_input + row['dry b'] y_w = row['wet a'] * x_input + row['wet b'] comparison_results.append({"name": row['model name'], "dry": y_d, "wet": y_w, "row": row}) if condition_view in ["Both", "Dry only"]: fig.add_trace(go.Scatter(x=x_range, y=row['dry a']*x_range+row['dry b'], mode='lines', name=f"{row['model name']} (Dry)", line=dict(color=color, width=4))) if n_dry > 0: xt = (n_dry - row['dry b']) / row['dry a'] if xt <= 200: fig.add_trace(go.Scatter(x=[xt], y=[n_dry], mode='markers+text', text=[f"{round(xt,1)}N"], textfont=dict(color="#000000", size=12, weight=700), textposition="top center", marker=dict(color=color, size=10, symbol='x'), showlegend=False)) if condition_view in ["Both", "Wet only"]: fig.add_trace(go.Scatter(x=x_range, y=row['wet a']*x_range+row['wet b'], mode='lines', name=f"{row['model name']} (Wet)", line=dict(color=color, width=3, dash='dot'))) if n_wet > 0: xtw = (n_wet - row['wet b']) / row['wet a'] if xtw <= 200: fig.add_trace(go.Scatter(x=[xtw], y=[n_wet], mode='markers+text', text=[f"{round(xtw,1)}N"], textfont=dict(color="#000000", size=12, weight=700), textposition="bottom center", marker=dict(color=color, size=10, symbol='circle-open'), showlegend=False)) # Lignes de normes if n_dry > 0 and (condition_view in ["Both", "Dry only"]): fig.add_hline(y=n_dry, line_width=3, line_color="#000000", annotation_text=f"Norm Dry: {n_dry}N", annotation_font=dict(color="black", size=12, weight=700)) if n_wet > 0 and (condition_view in ["Both", "Wet only"]): fig.add_hline(y=n_wet, line_width=3, line_dash="dot", line_color="#000000", annotation_text=f"Norm Wet: {n_wet}N", annotation_font=dict(color="black", size=12, weight=700)) fig.add_vline(x=x_input, line_width=2, line_dash="dash", line_color="#000000") fig.update_layout( height=480, xaxis=dict(title="Lever Effort [N]", color="#000000", linecolor="#000000", linewidth=3, tickfont=dict(color="#000000", size=13, weight=700), gridcolor="#E0E0E0"), yaxis=dict(title="Performance [N]", color="#000000", linecolor="#000000", linewidth=3, tickfont=dict(color="#000000", size=13, weight=700), gridcolor="#E0E0E0"), font=dict(color="#000000", size=12), plot_bgcolor='#FFFFFF', paper_bgcolor='#FFFFFF', hovermode="x unified", legend=dict(font=dict(color="#000000", size=12, weight=700), bordercolor="#000000", borderwidth=2, bgcolor="#FFFFFF") ) st.plotly_chart(fig, use_container_width=True) # --- ANALYSIS DASHBOARD --- st.markdown(f"

📊 Performance Analysis [N] | Ref: {ref_model}

", unsafe_allow_html=True) if not filtered_df.empty: cols = st.columns(len(comparison_results)) for i, res in enumerate(comparison_results): with cols[i]: st.markdown(f"

{res['name']} {'⭐' if (res['name'] == ref_model) else ''}

", unsafe_allow_html=True) if condition_view in ["Both", "Dry only"]: dv = round(res['dry'], 1) if enable_comparison and not (res['name'] == ref_model): diff = dv - round(ref_dry_val, 1) st.metric("Dry Perf.", f"{dv} N", f"{diff:+.1f} N Vs Ref.") else: st.metric("Dry Perf.", f"{dv} N") if n_dry > 0: xt = (n_dry - res['row']['dry b']) / res['row']['dry a'] if xt > 180: st.markdown(f"
❌ NON CONFORME SEC ({norm_type})
Target: {round(xt,1)}N > 180N
", unsafe_allow_html=True) else: st.markdown(f"
✅ Conforme Sec ({round(xt,1)}N)
", unsafe_allow_html=True) if condition_view in ["Both", "Wet only"]: wv = round(res['wet'], 1) if enable_comparison and not (res['name'] == ref_model): diffw = wv - round(ref_wet_val, 1) st.metric("Wet Perf.", f"{wv} N", f"{diffw:+.1f} N Vs Ref.") else: st.metric("Wet Perf.", f"{wv} N") if n_wet > 0: xtw = (n_wet - res['row']['wet b']) / res['row']['wet a'] if xtw > 180: st.markdown(f"
❌ NON CONFORME HUMIDE ({norm_type})
Target: {round(xtw,1)}N > 180N
", unsafe_allow_html=True) else: st.markdown(f"
✅ Conforme Humide ({round(xtw,1)}N)
", unsafe_allow_html=True) if show_loss and condition_view == "Both": loss_pct = ((res['dry'] - res['wet']) / res['dry'] * 100) if res['dry'] != 0 else 0 st.metric("Efficiency Loss", f"-{round(loss_pct, 1)}%", delta_color="inverse") except Exception as e: st.error(f"Error: {e}")