MaxBDKT commited on
Commit
3ff72d7
·
verified ·
1 Parent(s): 49f7133

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +63 -264
src/streamlit_app.py CHANGED
@@ -1,307 +1,106 @@
1
  import streamlit as st
2
  import pandas as pd
3
- import numpy as np
4
- import plotly.graph_objects as go
5
  import os
6
 
7
  # ==============================================================================
8
- # 1. CONFIGURATION ET VISIBILITÉ TOTALE (CSS BRUTE FORCE)
9
  # ==============================================================================
10
- st.set_page_config(
11
- page_title="Brake Performance Lab",
12
- layout="wide",
13
- page_icon="🚲"
14
- )
15
 
16
  st.markdown("""
17
  <style>
18
- /* 1.1 FOND BLANC ET TEXTE NOIR GLOBAL */
19
- .stApp, [data-testid="stSidebar"], .main {
20
- background-color: #FFFFFF !important;
21
- }
22
-
23
- /* 1.2 TYPOGRAPHIE : FORCE LE NOIR SUR TOUT */
24
- h1, h2, h3, h4, h5, h6, p, li, label, span, div, .stMarkdown {
25
- color: #000000 !important;
26
- font-family: 'Arial', sans-serif !important;
27
- }
28
-
29
- /* 1.3 SIDEBAR : CONTRASTE ET ESPACEMENT */
30
- [data-testid="stSidebar"] [data-testid="stVerticalBlock"] {
31
- gap: 0.1rem !important;
32
- padding-top: 0rem !important;
33
- }
34
- hr {
35
- border-top: 1px solid #000 !important;
36
- margin: 0.5rem 0 !important;
37
- }
38
-
39
- /* 1.4 WIDGETS : SELECTBOX (FIX NOIR SUR NOIR) */
40
- div[data-baseweb="select"], div[role="combobox"], .stSelectbox {
41
- border: 2px solid #000000 !important;
42
- background-color: #FFFFFF !important;
43
- }
44
- div[data-baseweb="select"] * {
45
- color: #000000 !important;
46
- }
47
 
48
- /* MENU DÉROULANT (OPTIONS) */
49
- div[role="listbox"] ul li {
50
- background-color: #FFFFFF !important;
51
- color: #000000 !important;
52
- }
53
- div[role="listbox"] ul li:hover {
54
- background-color: #0082C3 !important;
55
- color: #FFFFFF !important;
56
- }
57
-
58
- /* 1.5 INPUTS : TEXTE BLANC SUR FOND NOIR (TA DEMANDE) */
59
- input[type="number"], .stNumberInput div[data-baseweb="input"] input {
60
  color: #FFFFFF !important;
61
  background-color: #1E1E1E !important;
62
- border-radius: 4px !important;
63
  font-weight: bold !important;
64
- border: 1px solid #000 !important;
 
 
65
  }
66
 
67
- /* 1.6 EXPANDERS ET SLIDERS */
68
- .streamlit-expanderHeader, .st-ae, .st-af, .st-ag, .st-ai {
69
- color: #000000 !important;
70
- font-weight: bold !important;
71
- }
72
-
73
- /* 1.7 DASHBOARD : COLONNES ET MÉTRIQUES */
74
- [data-testid="column"] {
75
- padding: 15px !important;
76
- border: 2px solid #000000 !important;
77
- border-radius: 10px !important;
78
- background-color: #FFFFFF !important;
79
- margin-bottom: 10px;
80
- }
81
- [data-testid="stMetricValue"] {
82
- font-weight: 900 !important;
83
- color: #000000 !important;
84
- }
85
-
86
- /* 1.8 NORMES : BADGES TEXTE BLANC / FOND COULEUR */
87
- .alert-red {
88
- color: #FFFFFF !important;
89
- background-color: #B71C1C !important;
90
- font-weight: 900 !important;
91
- padding: 10px;
92
- border-radius: 5px;
93
- text-align: center;
94
- margin-top: 5px;
95
- border: 1px solid #000;
96
  }
97
- .check-green {
98
- color: #FFFFFF !important;
99
- background-color: #1B5E20 !important;
100
- font-weight: 900 !important;
101
- padding: 10px;
102
- border-radius: 5px;
103
- text-align: center;
104
- margin-top: 5px;
105
- border: 1px solid #000;
106
  }
107
  </style>
108
  """, unsafe_allow_html=True)
109
 
110
  # ==============================================================================
111
- # 2. CHARGEMENT DES DONNÉES
112
  # ==============================================================================
113
  @st.cache_data
114
  def load_data():
115
  current_dir = os.path.dirname(__file__)
116
  file_path = os.path.join(current_dir, "Brake_Lab_Test_Data.xlsx")
117
- if not os.path.exists(file_path):
118
- return pd.DataFrame()
119
  df = pd.read_excel(file_path, sheet_name='Data')
120
  df.columns = df.columns.str.strip()
121
  return df
122
 
123
  df = load_data()
124
 
125
- if df.empty:
126
- st.error("ERREUR : Fichier Excel manquant.")
127
- st.stop()
128
-
129
  # ==============================================================================
130
- # 3. BARRE LATÉRALE (SIDEBAR)
131
  # ==============================================================================
132
- try:
133
- all_models = df['model name'].unique().tolist()
 
134
 
135
- with st.sidebar:
136
- st.image("https://upload.wikimedia.org/wikipedia/commons/thumb/0/08/Decathlon_Logo.svg/1280px-Decathlon_Logo.svg.png", width=150)
137
- st.title("⚙️ CONFIGURATION")
138
-
139
- # Saisie Effort Principal
140
- effort_x = st.slider("🫱 Effort au Levier [N]", 40, 200, 100)
141
-
142
- # Sélection Modèles
143
- selected_models = st.multiselect("Modèles à comparer", options=all_models, default=all_models[:2])
144
-
145
- # Sélection Norme
146
- norm_cat = st.selectbox("Catégorie Norme", ["Aucune", "City/Trekking", "Kids", "MTB", "Racing"])
147
-
148
- st.markdown("---")
149
-
150
- # SECTION SIMULATION DÉROULANTE
151
- with st.expander("📏 Braking Simulation", expanded=False):
152
- v_kmh = st.number_input("Vitesse (km/h)", value=25)
153
- m_total = st.number_input("Masse Totale (kg)", value=100)
154
- rim_inch = st.selectbox("Taille de roue (inch)", [20, 24, 26, 27.5, 28, 29], index=4)
155
- sharing_av = st.slider("Répartition AV (%)", 0, 100, 70)
156
 
157
- # SECTION OPTIONS D'AFFICHAGE DÉROULANTE
158
- with st.expander("🔍 Options d'affichage", expanded=False):
159
- ref_model_name = st.selectbox("Benchmark de référence", options=all_models)
160
- view_mode = st.radio("Mode de vue", ["Sec & Humide", "Sec uniquement", "Humide uniquement"], index=0)
161
 
162
- # ==========================================================================
163
- # 4. MOTEUR PHYSIQUE (CONVERSIONS ET DYNAMIQUE)
164
- # ==========================================================================
165
- # 4.1 Conversion km/h vers m/s
166
- v_ms = v_kmh / 3.6
167
-
168
- # 4.2 Conversion inch vers mètres
169
- r_m = (rim_inch * 0.0254) / 2
170
-
171
- # 4.3 Énergie Cinétique de Translation
172
- e_trans = 0.5 * m_total * (v_ms**2)
173
-
174
- # 4.4 Énergie Cinétique de Rotation (2 roues)
175
- # Masse estimée jante+pneu = 1.5kg
176
- j_wheel = 1.5 * (r_m ** 2)
177
- omega = v_ms / r_m
178
- e_rot = 2 * (0.5 * j_wheel * (omega**2))
179
-
180
- # 4.5 Énergie Totale du Système
181
- e_total_j = e_trans + e_rot
182
-
183
- # 4.6 Définition des Seuils de Normes
184
- n_s = 0
185
- n_w = 0
186
- if norm_cat == "City/Trekking":
187
- n_s, n_w = 340, 220
188
- elif norm_cat == "Kids":
189
- n_s, n_w = 204, 132
190
- elif norm_cat == "MTB":
191
- n_s, n_w = 425, 280
192
- elif norm_cat == "Racing":
193
- n_s, n_w = 425, 260
194
-
195
- # 4.7 Données du Benchmark
196
- row_bench = df[df['model name'] == ref_model_name].iloc[0]
197
- ref_f_sec = row_bench['dry a'] * effort_x + row_bench['dry b']
198
- ref_f_wet = row_bench['wet a'] * effort_x + row_bench['wet b']
199
 
200
- # ==========================================================================
201
- # 5. HEADER : DIAGNOSTIC VISUEL
202
- # ==========================================================================
203
- label_diag = "❄️ FREINAGE LÉGER"
204
- color_diag = "#a1c4fd"
205
- if 70 <= effort_x <= 110:
206
- label_diag = "⚖️ FREINAGE MODÉRÉ"
207
- color_diag = "#ffdb58"
208
- elif effort_x > 110:
209
- label_diag = "🔥 FREINAGE PUISSANT"
210
- color_diag = "#ff4b4b"
211
-
212
- st.markdown(f"""
213
- <div style='background-color:{color_diag}; padding:10px; border-radius:8px; text-align:center; border: 3px solid #000; margin-bottom: 10px;'>
214
- <span style='font-weight:900; font-size:18px;'>{label_diag} | Effort: {effort_x} N</span>
215
- </div>
216
- """, unsafe_allow_html=True)
217
-
218
- # ==========================================================================
219
- # 6. GRAPHIQUE INTERACTIF (PLOTLY NOIR SUR BLANC)
220
- # ==========================================================================
221
- filtered_df = df[df['model name'].isin(selected_models)]
222
- fig = go.Figure()
223
- x_grid = np.linspace(40, 200, 150)
224
- color_p = ['#0082C3', '#E63312', '#333333', '#00A14B', '#FFD200']
225
-
226
- results_summary = []
227
-
228
- for i, (idx, row) in enumerate(filtered_df.iterrows()):
229
- clr = color_p[i % len(color_p)]
230
- f_sec = row['dry a'] * effort_x + row['dry b']
231
- f_wet = row['wet a'] * effort_x + row['wet b']
232
- results_summary.append({"name": row['model name'], "dry": f_sec, "wet": f_wet, "row": row})
233
-
234
- # Courbe Sec
235
- if view_mode != "Humide uniquement":
236
- fig.add_trace(go.Scatter(x=x_grid, y=row['dry a']*x_grid + row['dry b'], name=f"{row['model name']} (Sec)", line=dict(color=clr, width=4)))
237
- if n_s > 0:
238
- xt = (n_s - row['dry b']) / row['dry a']
239
- if 40 <= xt <= 200:
240
- fig.add_trace(go.Scatter(x=[xt], y=[n_s], mode='markers+text', text=[f"{round(xt,1)}N"], textfont=dict(color="black", weight=800), textposition="top center", marker=dict(color=clr, size=12, symbol='x'), showlegend=False))
241
-
242
- # Courbe Humide
243
- if view_mode != "Sec uniquement":
244
- fig.add_trace(go.Scatter(x=x_grid, y=row['wet a']*x_grid + row['wet b'], name=f"{row['model name']} (Wet)", line=dict(color=clr, width=2, dash='dot')))
245
-
246
- # Lignes de normes horizontales noires
247
- if n_s > 0 and view_mode != "Humide uniquement":
248
- fig.add_hline(y=n_s, line_width=3, line_color="#000", annotation_text="NORME SEC", annotation_font=dict(color="black", size=14, weight=800))
249
- if n_w > 0 and view_mode != "Sec uniquement":
250
- fig.add_hline(y=n_w, line_width=3, line_dash="dot", line_color="#000", annotation_text="NORME HUMIDE", annotation_font=dict(color="black", size=14, weight=800))
251
-
252
- # Ligne d'effort vertical
253
- fig.add_vline(x=effort_x, line_width=2, line_dash="dash", line_color="#555")
254
 
255
- # Mise en forme graphique (Forçage Noir)
256
- fig.update_layout(
257
- height=500, plot_bgcolor='white', paper_bgcolor='white',
258
- xaxis=dict(title=dict(text="Effort Levier [N]", font=dict(color="black", size=14, weight=700)), tickfont=dict(color="black", weight=700), linecolor="black", gridcolor="#EEE"),
259
- yaxis=dict(title=dict(text="Performance [N]", font=dict(color="black", size=14, weight=700)), tickfont=dict(color="black", weight=700), linecolor="black", gridcolor="#EEE"),
260
- legend=dict(font=dict(color="black", weight=700), bordercolor="black", borderwidth=1, bgcolor="white")
261
- )
262
- st.plotly_chart(fig, use_container_width=True)
263
 
264
- # ==========================================================================
265
- # 7. DASHBOARD D'ANALYSE (COMPARAISON ET DISTANCE)
266
- # ==========================================================================
267
- st.markdown(f"**📊 Dynamics Analysis | Energie du système : {int(e_total_j)} Joules**")
268
 
269
- cols = st.columns(len(results_summary))
 
 
 
 
 
 
 
 
 
270
 
271
- for i, res in enumerate(results_summary):
272
- with cols[i]:
273
- st.markdown(f"### {res['name']}")
274
-
275
- # --- ANALYSE CONDITION SEC ---
276
- if view_mode != "Humide uniquement":
277
- delta_s = round(res['dry'] - ref_f_sec, 1) if res['name'] != ref_model_name else None
278
- st.metric("Force Sec", f"{round(res['dry'], 1)} N", delta_s)
279
-
280
- # Calcul de la distance d'arrêt (D = E/F)
281
- d_sec = e_total_j / res['dry'] if res['dry'] > 0 else 0
282
- st.write(f"🛑 **Stop (Sec): {round(d_sec, 2)} m**")
283
-
284
- if n_s > 0:
285
- xt_req = (n_s - res['row']['dry b']) / res['row']['dry a']
286
- if xt_req > 180:
287
- st.markdown(f"<div class='alert-red'>NON CONFORME SEC</div>", unsafe_allow_html=True)
288
- else:
289
- st.markdown(f"<div class='check-green'>CONFORME SEC</div>", unsafe_allow_html=True)
290
-
291
- # --- ANALYSE CONDITION HUMIDE ---
292
- if view_mode != "Sec uniquement":
293
- delta_w = round(res['wet'] - ref_f_wet, 1) if res['name'] != ref_model_name else None
294
- st.metric("Force Wet", f"{round(res['wet'], 1)} N", delta_w)
295
-
296
- d_wet = e_total_j / res['wet'] if res['wet'] > 0 else 0
297
- st.write(f"🌧️ **Stop (Wet): {round(d_wet, 2)} m**")
298
-
299
- if n_w > 0:
300
- xtw_req = (n_w - res['row']['wet b']) / res['row']['wet a']
301
- if xtw_req > 180:
302
- st.markdown(f"<div class='alert-red'>NON CONFORME HUMIDE</div>", unsafe_allow_html=True)
303
- else:
304
- st.markdown(f"<div class='check-green'>CONFORME HUMIDE</div>", unsafe_allow_html=True)
305
 
306
- except Exception as e:
307
- st.error(f"Erreur Détectée : {e}")
 
1
  import streamlit as st
2
  import pandas as pd
 
 
3
  import os
4
 
5
  # ==============================================================================
6
+ # 1. STYLE CSS V2 (FOCUS LISIBILITÉ & SAISIE)
7
  # ==============================================================================
8
+ st.set_page_config(page_title="Brake Lab V2", layout="centered")
 
 
 
 
9
 
10
  st.markdown("""
11
  <style>
12
+ .stApp { background-color: #FFFFFF !important; }
13
+ * { color: #000000 !important; font-family: 'Arial', sans-serif; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
+ /* STYLE INPUT : Texte BLANC sur fond NOIR (Ta signature) */
16
+ input[type="number"] {
 
 
 
 
 
 
 
 
 
 
17
  color: #FFFFFF !important;
18
  background-color: #1E1E1E !important;
19
+ border-radius: 8px !important;
20
  font-weight: bold !important;
21
+ font-size: 20px !important;
22
+ height: 60px !important;
23
+ text-align: center !important;
24
  }
25
 
26
+ /* Boite de résultat Performance */
27
+ .perf-box {
28
+ padding: 30px;
29
+ border: 4px solid #000000;
30
+ border-radius: 15px;
31
+ background-color: #FFFFFF;
32
+ text-align: center;
33
+ margin-top: 20px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  }
35
+ .perf-value {
36
+ font-size: 50px;
37
+ font-weight: 900;
38
+ color: #0082C3 !important;
 
 
 
 
 
39
  }
40
  </style>
41
  """, unsafe_allow_html=True)
42
 
43
  # ==============================================================================
44
+ # 2. DATA
45
  # ==============================================================================
46
  @st.cache_data
47
  def load_data():
48
  current_dir = os.path.dirname(__file__)
49
  file_path = os.path.join(current_dir, "Brake_Lab_Test_Data.xlsx")
50
+ if not os.path.exists(file_path): return pd.DataFrame()
 
51
  df = pd.read_excel(file_path, sheet_name='Data')
52
  df.columns = df.columns.str.strip()
53
  return df
54
 
55
  df = load_data()
56
 
 
 
 
 
57
  # ==============================================================================
58
+ # 3. INTERFACE V2 : SAISIE & CALCUL
59
  # ==============================================================================
60
+ if not df.empty:
61
+ st.title("🎯 Brake Performance Finder")
62
+ st.write("Entrez l'effort levier et choisissez votre modèle pour obtenir la performance exacte.")
63
 
64
+ col1, col2 = st.columns(2)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
 
66
+ with col1:
67
+ # Saisie manuelle de la valeur (40-200N)
68
+ effort_val = st.number_input("Effort Levier [N]", min_value=40, max_value=200, value=100, step=1)
 
69
 
70
+ with col2:
71
+ # Sélection du modèle
72
+ model_list = df['model name'].unique().tolist()
73
+ model_choice = st.selectbox("Modèle à tester", options=model_list)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
 
75
+ # --- CALCUL ---
76
+ row = df[df['model name'] == model_choice].iloc[0]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
 
78
+ # Formule linéaire : y = ax + b
79
+ res_dry = row['dry a'] * effort_val + row['dry b']
80
+ res_wet = row['wet a'] * effort_val + row['wet b']
 
 
 
 
 
81
 
82
+ # --- AFFICHAGE DES RÉSULTATS ---
83
+ st.markdown("---")
 
 
84
 
85
+ res_col1, res_col2 = st.columns(2)
86
+
87
+ with res_col1:
88
+ st.markdown(f"""
89
+ <div class="perf-box">
90
+ <p style="font-size: 20px; font-weight: bold;">SOL SEC</p>
91
+ <p class="perf-value">{round(res_dry, 1)} N</p>
92
+ <p>Force de freinage</p>
93
+ </div>
94
+ """, unsafe_allow_html=True)
95
 
96
+ with res_col2:
97
+ st.markdown(f"""
98
+ <div class="perf-box">
99
+ <p style="font-size: 20px; font-weight: bold;">SOL HUMIDE</p>
100
+ <p class="perf-value" style="color: #E63312 !important;">{round(res_wet, 1)} N</p>
101
+ <p>Force de freinage</p>
102
+ </div>
103
+ """, unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
 
105
+ else:
106
+ st.error("Fichier de données non trouvé.")