MaxBDKT commited on
Commit
20fb3de
Β·
verified Β·
1 Parent(s): 0a0c15b

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +74 -36
src/streamlit_app.py CHANGED
@@ -7,13 +7,14 @@ import os
7
  # Page Configuration
8
  st.set_page_config(page_title="Brake Performance Lab", layout="wide", page_icon="🚲")
9
 
10
- # Style CSS
11
  st.markdown("""
12
  <style>
13
  .small-font { font-size:12px !important; color: black !important; }
14
  [data-testid="stMetricValue"] { font-size: 18px !important; color: black !important; }
15
- [data-testid="column"] { padding: 5px !important; border: 1px solid #000000; border-radius: 5px; }
16
- .alert-red { color: #ff4b4b; font-weight: bold; font-size: 12px; margin-top: 5px; }
 
17
  .check-green { color: #00A14B; font-weight: bold; font-size: 11px; }
18
  h1, h2, h3, h4, p, span { color: black !important; }
19
  </style>
@@ -31,6 +32,7 @@ try:
31
  df = load_data()
32
  all_models = df['model name'].unique().tolist()
33
 
 
34
  with st.sidebar:
35
  st.image("https://upload.wikimedia.org/wikipedia/commons/thumb/0/08/Decathlon_Logo.svg/1280px-Decathlon_Logo.svg.png", width=200)
36
  st.title("βš™οΈ Settings")
@@ -48,11 +50,26 @@ try:
48
  elif norm_type == "MTB": n_dry, n_wet = 425, 280
49
  elif norm_type == "Racing": n_dry, n_wet = 425, 260
50
 
 
51
  with st.expander("πŸ” Display Options"):
52
  show_loss = st.checkbox("Show Wet Loss Analysis", value=True)
53
  enable_comparison = st.checkbox("Enable Reference Comparison", value=True)
54
- ref_model = st.selectbox("Reference Model", options=all_models)
55
- condition_view = st.radio("Conditions", ["Both", "Dry only", "Wet only"], index=0)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
 
57
  # --- GRAPHIC AREA ---
58
  filtered_df = df[df['model name'].isin(selected_models)]
@@ -72,59 +89,80 @@ try:
72
  comparison_results.append({"name": row['model name'], "dry": y_dry_now, "wet": y_wet_now, "row": row})
73
 
74
  if condition_view in ["Both", "Dry only"]:
75
- 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)))
76
  if n_dry > 0:
77
  x_t = (n_dry - row['dry b']) / row['dry a']
78
- if x_t <= 200: # On ne trace que si c'est visible
79
  fig.add_trace(go.Scatter(x=[x_t], y=[n_dry], mode='markers+text', text=[f"{round(x_t,1)}N"], textposition="top center", marker=dict(color=color, size=10, symbol='x'), showlegend=False))
80
 
81
  if condition_view in ["Both", "Wet only"]:
82
- 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=2, dash='dot')))
83
  if n_wet > 0:
84
  x_t_w = (n_wet - row['wet b']) / row['wet a']
85
  if x_t_w <= 200:
86
  fig.add_trace(go.Scatter(x=[x_t_w], y=[n_wet], mode='markers+text', text=[f"{round(x_t_w,1)}N"], textposition="bottom center", marker=dict(color=color, size=10, symbol='circle-open'), showlegend=False))
87
 
88
- if n_dry > 0 and condition_view in ["Both", "Dry only"]:
89
- fig.add_hline(y=n_dry, line_width=2, line_color="#000000", annotation_text=f"Norm Dry: {n_dry}N")
90
- if n_wet > 0 and condition_view in ["Both", "Wet only"]:
91
- fig.add_hline(y=n_wet, line_width=2, line_dash="dot", line_color="#000000", annotation_text=f"Norm Wet: {n_wet}N")
92
 
93
- fig.update_layout(height=450, xaxis_title="Lever Effort [N]", yaxis_title="Performance [N]", font=dict(color="#000000"), plot_bgcolor='white', paper_bgcolor='white', hovermode="x unified")
 
 
 
94
  st.plotly_chart(fig, use_container_width=True)
95
 
96
- # --- ANALYSIS DASHBOARD ---
 
 
97
  if not filtered_df.empty:
98
  cols = st.columns(len(comparison_results))
99
  for i, res in enumerate(comparison_results):
100
  with cols[i]:
101
- st.markdown(f"<p style='font-size:13px; font-weight:bold; color:black; margin-bottom:10px;'>{res['name']}</p>", unsafe_allow_html=True)
 
102
 
103
- compliance_issue = False
104
-
105
- # Check Dry Norm
106
- if n_dry > 0 and condition_view in ["Both", "Dry only"]:
107
- x_target = (n_dry - res['row']['dry b']) / res['row']['dry a']
108
- st.metric("Dry Perf.", f"{round(res['dry'],1)} N")
109
- if x_target > 180:
110
- st.markdown(f"<div class='alert-red'>❌ NON CONFORME SEC ({norm_type})<br>Effort requis: {round(x_target,1)}N > 180N</div>", unsafe_allow_html=True)
111
- compliance_issue = True
112
  else:
113
- st.markdown(f"<div class='check-green'>βœ… Conforme Sec ({round(x_target,1)}N)</div>", unsafe_allow_html=True)
114
-
115
- # Check Wet Norm
116
- if n_wet > 0 and condition_view in ["Both", "Wet only"]:
117
- x_target_w = (n_wet - res['row']['wet b']) / res['row']['wet a']
118
- st.metric("Wet Perf.", f"{round(res['wet'],1)} N")
119
- if x_target_w > 180:
120
- st.markdown(f"<div class='alert-red'>❌ NON CONFORME HUMIDE ({norm_type})<br>Effort requis: {round(x_target_w,1)}N > 180N</div>", unsafe_allow_html=True)
121
- compliance_issue = True
 
 
 
 
 
 
 
 
122
  else:
123
- st.markdown(f"<div class='check-green'>βœ… Conforme Humide ({round(x_target_w,1)}N)</div>", unsafe_allow_html=True)
 
 
 
 
 
 
 
 
124
 
 
125
  if show_loss and condition_view == "Both":
126
  loss_pct = ((res['dry'] - res['wet']) / res['dry'] * 100) if res['dry'] != 0 else 0
127
- st.metric("Wet Loss", f"-{round(loss_pct, 1)}%")
128
 
129
  except Exception as e:
130
- st.error(f"Error: {e}")
 
7
  # Page Configuration
8
  st.set_page_config(page_title="Brake Performance Lab", layout="wide", page_icon="🚲")
9
 
10
+ # Style CSS Global
11
  st.markdown("""
12
  <style>
13
  .small-font { font-size:12px !important; color: black !important; }
14
  [data-testid="stMetricValue"] { font-size: 18px !important; color: black !important; }
15
+ [data-testid="stMetricDelta"] { font-size: 12px !important; }
16
+ [data-testid="column"] { padding: 8px !important; border: 1px solid #000000; border-radius: 5px; background-color: #ffffff; }
17
+ .alert-red { color: #ff4b4b; font-weight: bold; font-size: 11px; margin-top: 5px; }
18
  .check-green { color: #00A14B; font-weight: bold; font-size: 11px; }
19
  h1, h2, h3, h4, p, span { color: black !important; }
20
  </style>
 
32
  df = load_data()
33
  all_models = df['model name'].unique().tolist()
34
 
35
+ # --- SIDEBAR ---
36
  with st.sidebar:
37
  st.image("https://upload.wikimedia.org/wikipedia/commons/thumb/0/08/Decathlon_Logo.svg/1280px-Decathlon_Logo.svg.png", width=200)
38
  st.title("βš™οΈ Settings")
 
50
  elif norm_type == "MTB": n_dry, n_wet = 425, 280
51
  elif norm_type == "Racing": n_dry, n_wet = 425, 260
52
 
53
+ st.markdown("---")
54
  with st.expander("πŸ” Display Options"):
55
  show_loss = st.checkbox("Show Wet Loss Analysis", value=True)
56
  enable_comparison = st.checkbox("Enable Reference Comparison", value=True)
57
+ ref_model = st.selectbox("Reference Model (Benchmark)", options=all_models)
58
+ condition_view = st.radio("Conditions to display", ["Both", "Dry only", "Wet only"], index=0)
59
+
60
+ # --- DIAGNOSTIC HEADER ---
61
+ if x_input < 70:
62
+ label, color_alert = "❄️ LIGHT BRAKING", "#a1c4fd"
63
+ elif 70 <= x_input <= 110:
64
+ label, color_alert = "βš–οΈ MODERATE BRAKING", "#ffdb58"
65
+ else:
66
+ label, color_alert = "πŸ”₯ POWERFUL BRAKING", "#ff4b4b"
67
+
68
+ st.markdown(f"""
69
+ <div style="background-color:{color_alert}; padding:5px; border-radius:8px; text-align:center; border: 2px solid #000; margin-bottom: 10px;">
70
+ <span style="color:black; font-weight:bold; font-size:14px;">{label} | Effort: {round(float(x_input), 1)} N</span>
71
+ </div>
72
+ """, unsafe_allow_html=True)
73
 
74
  # --- GRAPHIC AREA ---
75
  filtered_df = df[df['model name'].isin(selected_models)]
 
89
  comparison_results.append({"name": row['model name'], "dry": y_dry_now, "wet": y_wet_now, "row": row})
90
 
91
  if condition_view in ["Both", "Dry only"]:
92
+ 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), hovertemplate=f"<b>{row['model name']}</b><br>Perf: %{{y:.1f}} N<extra></extra>"))
93
  if n_dry > 0:
94
  x_t = (n_dry - row['dry b']) / row['dry a']
95
+ if x_t <= 200:
96
  fig.add_trace(go.Scatter(x=[x_t], y=[n_dry], mode='markers+text', text=[f"{round(x_t,1)}N"], textposition="top center", marker=dict(color=color, size=10, symbol='x'), showlegend=False))
97
 
98
  if condition_view in ["Both", "Wet only"]:
99
+ 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=2, dash='dot'), hovertemplate=f"<b>{row['model name']}</b><br>Perf: %{{y:.1f}} N<extra></extra>"))
100
  if n_wet > 0:
101
  x_t_w = (n_wet - row['wet b']) / row['wet a']
102
  if x_t_w <= 200:
103
  fig.add_trace(go.Scatter(x=[x_t_w], y=[n_wet], mode='markers+text', text=[f"{round(x_t_w,1)}N"], textposition="bottom center", marker=dict(color=color, size=10, symbol='circle-open'), showlegend=False))
104
 
105
+ if n_dry > 0 and (condition_view in ["Both", "Dry only"]):
106
+ fig.add_hline(y=n_dry, line_width=2, line_color="#000", annotation_text=f"Norm Dry: {n_dry}N")
107
+ if n_wet > 0 and (condition_view in ["Both", "Wet only"]):
108
+ fig.add_hline(y=n_wet, line_width=2, line_dash="dot", line_color="#000", annotation_text=f"Norm Wet: {n_wet}N")
109
 
110
+ fig.add_vline(x=x_input, line_width=2, line_dash="dash", line_color="#000")
111
+ fig.update_layout(height=450, xaxis_title="Lever Effort [N]", yaxis_title="Performance [N]", font=dict(color="#000"), plot_bgcolor='white', paper_bgcolor='white', hovermode="x unified", legend=dict(font=dict(color="#000"), bordercolor="#000", borderwidth=1))
112
+ fig.update_xaxes(showline=True, linewidth=2, linecolor='#000', gridcolor='#EEE')
113
+ fig.update_yaxes(showline=True, linewidth=2, linecolor='#000', gridcolor='#EEE')
114
  st.plotly_chart(fig, use_container_width=True)
115
 
116
+ # --- ANALYSIS DASHBOARD (BOTTOM) ---
117
+ st.markdown(f"<p class='small-font'><b>πŸ“Š Performance Analysis [N]</b> | Ref: {ref_model}</p>", unsafe_allow_html=True)
118
+
119
  if not filtered_df.empty:
120
  cols = st.columns(len(comparison_results))
121
  for i, res in enumerate(comparison_results):
122
  with cols[i]:
123
+ is_ref = (res['name'] == ref_model)
124
+ st.markdown(f"<p style='font-size:13px; font-weight:bold; color:black; margin-bottom:5px;'>{res['name']} {'⭐' if is_ref else ''}</p>", unsafe_allow_html=True)
125
 
126
+ # --- DRY PERFORMANCE & BENCHMARK ---
127
+ if condition_view in ["Both", "Dry only"]:
128
+ d_val = round(res['dry'], 1)
129
+ if enable_comparison and not is_ref:
130
+ diff = d_val - round(ref_dry_val, 1)
131
+ pct = (diff / ref_dry_val * 100) if ref_dry_val != 0 else 0
132
+ st.metric("Dry Perf.", f"{d_val} N", f"{diff:+.1f} N ({pct:+.1f}%) Vs Ref.")
 
 
133
  else:
134
+ st.metric("Dry Perf.", f"{d_val} N")
135
+
136
+ # NORM CHECK DRY
137
+ if n_dry > 0:
138
+ x_target = (n_dry - res['row']['dry b']) / res['row']['dry a']
139
+ if x_target > 180:
140
+ st.markdown(f"<div class='alert-red'>❌ NON CONFORME SEC ({norm_type})<br>Target: {round(x_target,1)}N > 180N</div>", unsafe_allow_html=True)
141
+ else:
142
+ st.markdown(f"<div class='check-green'>βœ… Conforme Sec ({round(x_target,1)}N)</div>", unsafe_allow_html=True)
143
+
144
+ # --- WET PERFORMANCE & BENCHMARK ---
145
+ if condition_view in ["Both", "Wet only"]:
146
+ w_val = round(res['wet'], 1)
147
+ if enable_comparison and not is_ref:
148
+ diff_w = w_val - round(ref_wet_val, 1)
149
+ pct_w = (diff_w / ref_wet_val * 100) if ref_wet_val != 0 else 0
150
+ st.metric("Wet Perf.", f"{w_val} N", f"{diff_w:+.1f} N ({pct_w:+.1f}%) Vs Ref.")
151
  else:
152
+ st.metric("Wet Perf.", f"{w_val} N")
153
+
154
+ # NORM CHECK WET
155
+ if n_wet > 0:
156
+ x_target_w = (n_wet - res['row']['wet b']) / res['row']['wet a']
157
+ if x_target_w > 180:
158
+ st.markdown(f"<div class='alert-red'>❌ NON CONFORME HUMIDE ({norm_type})<br>Target: {round(x_target_w,1)}N > 180N</div>", unsafe_allow_html=True)
159
+ else:
160
+ st.markdown(f"<div class='check-green'>βœ… Conforme Humide ({round(x_target_w,1)}N)</div>", unsafe_allow_html=True)
161
 
162
+ # --- WET LOSS ---
163
  if show_loss and condition_view == "Both":
164
  loss_pct = ((res['dry'] - res['wet']) / res['dry'] * 100) if res['dry'] != 0 else 0
165
+ st.metric("Efficiency Loss", f"-{round(loss_pct, 1)}%", f"{round(res['wet']-res['dry'], 1)} N vs Dry", delta_color="inverse")
166
 
167
  except Exception as e:
168
+ st.error(f"Error: {e}")