File size: 9,839 Bytes
70724bb
23d5a62
 
 
c37f22d
23d5a62
f3c7286
dfdc829
23d5a62
682f46c
03b6841
 
682f46c
 
 
 
 
 
 
 
 
 
 
 
f3c7286
682f46c
f3c7286
682f46c
 
 
 
 
 
 
f3c7286
 
 
 
 
 
682f46c
f3c7286
682f46c
 
 
 
03b6841
 
 
eb93b5a
23d5a62
c37f22d
 
6ac1af8
 
 
23d5a62
 
 
03b6841
23d5a62
dfdc829
 
f3c7286
03b6841
 
0a0c15b
dfdc829
0a0c15b
 
 
 
 
 
 
 
 
23d5a62
0a0c15b
 
 
20fb3de
 
 
 
f3c7286
 
 
20fb3de
 
f3c7286
 
20fb3de
 
23d5a62
03b6841
 
 
0a0c15b
682f46c
f3c7286
03b6841
 
 
 
 
 
 
 
f3c7286
 
 
7d75a7b
03b6841
682f46c
0a0c15b
f3c7286
682f46c
0a0c15b
03b6841
682f46c
0a0c15b
f3c7286
682f46c
0a0c15b
f3c7286
20fb3de
682f46c
20fb3de
682f46c
03b6841
f3c7286
 
 
 
605d964
 
f3c7286
682f46c
f3c7286
682f46c
f3c7286
03b6841
 
682f46c
 
20fb3de
03b6841
 
 
 
682f46c
0a0c15b
20fb3de
f3c7286
682f46c
f3c7286
682f46c
f3c7286
20fb3de
 
f3c7286
 
 
20fb3de
 
f3c7286
682f46c
f3c7286
682f46c
f3c7286
20fb3de
 
f3c7286
 
 
03b6841
 
0a0c15b
682f46c
70724bb
23d5a62
20fb3de
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
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}")