Brake_Lab_Test / src /streamlit_app.py
MaxBDKT's picture
Update src/streamlit_app.py
682f46c verified
raw
history blame
9.84 kB
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("""
<style>
/* 1. On force le fond de toute la page en BLANC */
.stApp {
background-color: #FFFFFF !important;
}
/* 2. On force TOUT le texte en NOIR ABSOLU */
* {
color: #000000 !important;
}
/* 3. On force les étiquettes des menus (Selectbox, Slider, Multiselect) en NOIR */
label, .stMultiSelect div, .stSelectbox div, .stSlider div {
color: #000000 !important;
font-weight: bold !important;
}
/* 4. On s'assure que les entrées de texte et menus ont un fond clair pour voir le texte noir dedans */
.stMultiSelect span, .stSelectbox div[data-baseweb="select"] {
background-color: #F0F2F6 !important;
}
/* 5. Metrics et boîtes d'analyse */
[data-testid="stMetricValue"] { color: #000000 !important; font-weight: 800 !important; font-size: 22px !important; }
[data-testid="stMetricLabel"] { color: #000000 !important; font-weight: bold !important; }
[data-testid="column"] {
padding: 10px !important;
border: 2px solid #000000 !important;
border-radius: 8px !important;
background-color: #FFFFFF !important;
}
/* 6. Alertes (on garde les couleurs mais en version foncée pour la lisibilité) */
.alert-red { color: #B71C1C !important; font-weight: 900 !important; }
.check-green { color: #1B5E20 !important; font-weight: 900 !important; }
</style>
""", 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"""
<div style="background-color:{color_alert}; padding:10px; border-radius:8px; text-align:center; border: 3px solid #000000; margin-bottom: 15px;">
<span style="color:#000000 !important; font-weight:900; font-size:16px;">{label} | Effort: {round(float(x_input), 1)} N</span>
</div>
""", 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"<p style='color:#000000; font-weight:900; font-size:16px;'>📊 Performance Analysis [N] | Ref: {ref_model}</p>", 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"<p style='font-size:14px; font-weight:900; color:#000000; margin-bottom:5px; text-decoration: underline;'>{res['name']} {'⭐' if (res['name'] == ref_model) else ''}</p>", 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"<div class='alert-red'>❌ NON CONFORME SEC ({norm_type})<br>Target: {round(xt,1)}N > 180N</div>", unsafe_allow_html=True)
else: st.markdown(f"<div class='check-green'>✅ Conforme Sec ({round(xt,1)}N)</div>", 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"<div class='alert-red'>❌ NON CONFORME HUMIDE ({norm_type})<br>Target: {round(xtw,1)}N > 180N</div>", unsafe_allow_html=True)
else: st.markdown(f"<div class='check-green'>✅ Conforme Humide ({round(xtw,1)}N)</div>", 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}")