MaxBDKT commited on
Commit
0a0c15b
Β·
verified Β·
1 Parent(s): 03b6841

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +66 -74
src/streamlit_app.py CHANGED
@@ -7,25 +7,15 @@ import os
7
  # Page Configuration
8
  st.set_page_config(page_title="Brake Performance Lab", layout="wide", page_icon="🚲")
9
 
10
- # Style CSS pour rΓ©duire la police et compacter l'affichage
11
  st.markdown("""
12
  <style>
13
- .small-font {
14
- font-size:12px !important;
15
- color: black !important;
16
- }
17
- [data-testid="stMetricValue"] {
18
- font-size: 20px !important;
19
- color: black !important;
20
- }
21
- [data-testid="stMetricDelta"] {
22
- font-size: 13px !important;
23
- }
24
- [data-testid="column"] {
25
- padding: 5px !important;
26
- border: 1px solid #f0f2f6;
27
- border-radius: 5px;
28
- }
29
  </style>
30
  """, unsafe_allow_html=True)
31
 
@@ -41,41 +31,35 @@ try:
41
  df = load_data()
42
  all_models = df['model name'].unique().tolist()
43
 
44
- # --- SIDEBAR ---
45
  with st.sidebar:
46
  st.image("https://upload.wikimedia.org/wikipedia/commons/thumb/0/08/Decathlon_Logo.svg/1280px-Decathlon_Logo.svg.png", width=200)
47
  st.title("βš™οΈ Settings")
48
  x_input = st.slider("🫱 Lever Effort [N]", 40, 200, 100)
49
  st.markdown("---")
50
- with st.expander("πŸ” Display Options"):
51
- show_loss = st.checkbox("Show Wet Loss (%)", value=True)
52
- enable_comparison = st.checkbox("Enable Ref. Comparison", value=False)
53
- ref_model = st.selectbox("Reference Model", options=all_models)
54
- condition_view = st.radio("Conditions to display", ["Both", "Dry only", "Wet only"], index=0)
55
  st.markdown("---")
56
- selected_models = st.multiselect("Select Models", options=all_models, default=all_models[:2])
57
-
58
- # --- DIAGNOSTIC HEADER (COMPACT) ---
59
- if x_input < 70:
60
- label, color_alert = "❄️ LIGHT BRAKING", "#a1c4fd"
61
- elif 70 <= x_input <= 110:
62
- label, color_alert = "βš–οΈ MODERATE BRAKING", "#ffdb58"
63
- else:
64
- label, color_alert = "πŸ”₯ POWERFUL BRAKING", "#ff4b4b"
65
 
66
- st.markdown(f"""
67
- <div style="background-color:{color_alert}; padding:5px; border-radius:8px; text-align:center; border: 1.5px solid #333; margin-bottom: 10px;">
68
- <span style="color:black; font-weight:bold; font-size:14px;">{label} | Effort: {round(float(x_input), 1)} N</span>
69
- </div>
70
- """, unsafe_allow_html=True)
71
 
72
  # --- GRAPHIC AREA ---
73
  filtered_df = df[df['model name'].isin(selected_models)]
74
  fig = go.Figure()
75
- x_range = np.linspace(40, 200, 100)
76
  colors = ['#0082C3', '#E63312', '#333333', '#00A14B', '#FFD200']
77
 
78
- # Reference data
79
  row_ref = df[df['model name'] == ref_model].iloc[0]
80
  ref_dry_val = row_ref['dry a'] * x_input + row_ref['dry b']
81
  ref_wet_val = row_ref['wet a'] * x_input + row_ref['wet b']
@@ -83,56 +67,64 @@ try:
83
  comparison_results = []
84
  for i, (index, row) in enumerate(filtered_df.iterrows()):
85
  color = colors[i % len(colors)]
86
- y_dry_val = row['dry a'] * x_input + row['dry b']
87
- y_wet_val = row['wet a'] * x_input + row['wet b']
88
- comparison_results.append({"name": row['model name'], "dry": y_dry_val, "wet": y_wet_val})
89
 
90
- y_plot_dry = row['dry a'] * x_range + row['dry b']
91
- y_plot_wet = row['wet a'] * x_range + row['wet b']
92
-
93
  if condition_view in ["Both", "Dry only"]:
94
- fig.add_trace(go.Scatter(x=x_range, y=y_plot_dry, 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>"))
 
 
 
 
 
95
  if condition_view in ["Both", "Wet only"]:
96
- fig.add_trace(go.Scatter(x=x_range, y=y_plot_wet, 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>"))
 
 
 
 
 
 
 
 
 
97
 
98
- fig.add_vline(x=x_input, line_width=2, line_dash="dash", line_color="black")
99
- fig.update_layout(height=450, xaxis_title="Lever Effort [N]", yaxis_title="Performance [N]", font=dict(color="black"), plot_bgcolor='white', paper_bgcolor='white', hovermode="x unified", legend=dict(font=dict(size=10, color="black"), bordercolor="black", borderwidth=1))
100
- fig.update_xaxes(showline=True, linewidth=2, linecolor='black', gridcolor='#EEEEEE')
101
- fig.update_yaxes(showline=True, linewidth=2, linecolor='black', gridcolor='#EEEEEE')
102
-
103
  st.plotly_chart(fig, use_container_width=True)
104
 
105
- # --- ANALYSIS DASHBOARD (BOTTOM) ---
106
- st.markdown(f"<p class='small-font'><b>πŸ“Š Dashboard Analysis [N]</b> | Reference: {ref_model}</p>", unsafe_allow_html=True)
107
-
108
  if not filtered_df.empty:
109
  cols = st.columns(len(comparison_results))
110
  for i, res in enumerate(comparison_results):
111
  with cols[i]:
112
- is_ref = (res['name'] == ref_model)
113
- st.markdown(f"<p style='font-size:13px; font-weight:bold; margin-bottom:0;'>{res['name']} {'⭐' if is_ref else ''}</p>", unsafe_allow_html=True)
 
114
 
115
- if condition_view in ["Both", "Dry only"]:
116
- d_val = round(res['dry'], 1)
117
- if enable_comparison and not is_ref:
118
- diff = d_val - round(ref_dry_val, 1)
119
- pct = (diff / ref_dry_val * 100) if ref_dry_val != 0 else 0
120
- st.metric("Dry Performance", f"{d_val} N", f"{diff:+.1f} N ({pct:+.1f}%)")
 
121
  else:
122
- st.metric("Dry Performance", f"{d_val} N")
123
 
124
- if condition_view in ["Both", "Wet only"]:
125
- w_val = round(res['wet'], 1)
126
- if enable_comparison and not is_ref:
127
- diff_w = w_val - round(ref_wet_val, 1)
128
- pct_w = (diff_w / ref_wet_val * 100) if ref_wet_val != 0 else 0
129
- st.metric("Wet Performance", f"{w_val} N", f"{diff_w:+.1f} N ({pct_w:+.1f}%)")
 
130
  else:
131
- st.metric("Wet Performance", f"{w_val} N")
132
 
133
  if show_loss and condition_view == "Both":
134
- loss = ((res['dry'] - res['wet']) / res['dry']) * 100 if res['dry'] != 0 else 0
135
- st.markdown(f"<p style='color:red; font-size:11px; margin-top:-10px;'>Wet Loss: -{round(loss, 1)}%</p>", unsafe_allow_html=True)
136
 
137
  except Exception as e:
138
- 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
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>
20
  """, unsafe_allow_html=True)
21
 
 
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")
37
  x_input = st.slider("🫱 Lever Effort [N]", 40, 200, 100)
38
  st.markdown("---")
39
+ selected_models = st.multiselect("Select Models to Display", options=all_models, default=all_models[:2])
 
 
 
 
40
  st.markdown("---")
41
+
42
+ st.subheader("πŸ“‹ Standard Compliance")
43
+ norm_type = st.selectbox("Category", ["None", "City/Trekking", "Kids", "MTB", "Racing"])
44
+
45
+ n_dry, n_wet = 0, 0
46
+ if norm_type == "City/Trekking": n_dry, n_wet = 340, 220
47
+ elif norm_type == "Kids": n_dry, n_wet = 204, 132
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)]
59
  fig = go.Figure()
60
+ x_range = np.linspace(40, 200, 150)
61
  colors = ['#0082C3', '#E63312', '#333333', '#00A14B', '#FFD200']
62
 
 
63
  row_ref = df[df['model name'] == ref_model].iloc[0]
64
  ref_dry_val = row_ref['dry a'] * x_input + row_ref['dry b']
65
  ref_wet_val = row_ref['wet a'] * x_input + row_ref['wet b']
 
67
  comparison_results = []
68
  for i, (index, row) in enumerate(filtered_df.iterrows()):
69
  color = colors[i % len(colors)]
70
+ y_dry_now = row['dry a'] * x_input + row['dry b']
71
+ y_wet_now = row['wet a'] * x_input + row['wet b']
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}")