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

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +70 -66
src/streamlit_app.py CHANGED
@@ -4,19 +4,32 @@ import numpy as np
4
  import plotly.graph_objects as go
5
  import os
6
 
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>
21
  """, unsafe_allow_html=True)
22
 
@@ -32,10 +45,9 @@ try:
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")
39
  x_input = st.slider("🫱 Lever Effort [N]", 40, 200, 100)
40
  st.markdown("---")
41
  selected_models = st.multiselect("Select Models to Display", options=all_models, default=all_models[:2])
@@ -50,7 +62,6 @@ try:
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)
@@ -58,16 +69,13 @@ try:
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
 
@@ -75,7 +83,7 @@ try:
75
  filtered_df = df[df['model name'].isin(selected_models)]
76
  fig = go.Figure()
77
  x_range = np.linspace(40, 200, 150)
78
- colors = ['#0082C3', '#E63312', '#333333', '#00A14B', '#FFD200']
79
 
80
  row_ref = df[df['model name'] == ref_model].iloc[0]
81
  ref_dry_val = row_ref['dry a'] * x_input + row_ref['dry b']
@@ -84,82 +92,78 @@ try:
84
  comparison_results = []
85
  for i, (index, row) in enumerate(filtered_df.iterrows()):
86
  color = colors[i % len(colors)]
87
- y_dry_now = row['dry a'] * x_input + row['dry b']
88
- y_wet_now = row['wet a'] * x_input + row['wet b']
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")
 
4
  import plotly.graph_objects as go
5
  import os
6
 
7
+ # Configuration de la page
8
  st.set_page_config(page_title="Brake Performance Lab", layout="wide", page_icon="🚲")
9
 
10
+ # --- FORÇAGE CSS NOIR ABSOLU POUR STREAMLIT ---
11
  st.markdown("""
12
  <style>
13
+ /* Tous les textes Streamlit en Noir pur */
14
+ html, body, [class*="css"], .stMarkdown, p, span, label {
15
+ color: #000000 !important;
16
+ font-weight: 500 !important;
17
+ }
18
+ /* Metrics en Noir pur */
19
+ [data-testid="stMetricValue"] { color: #000000 !important; font-weight: 800 !important; font-size: 22px !important; }
20
+ [data-testid="stMetricLabel"] { color: #000000 !important; font-weight: bold !important; }
21
+
22
+ /* Contours des boites d'analyse */
23
+ [data-testid="column"] {
24
+ padding: 10px !important;
25
+ border: 2px solid #000000 !important;
26
+ border-radius: 8px !important;
27
+ background-color: #ffffff !important;
28
+ }
29
+
30
+ /* Alerte rouge/verte bien flashy */
31
+ .alert-red { color: #D32F2F !important; font-weight: 900 !important; font-size: 13px; }
32
+ .check-green { color: #2E7D32 !important; font-weight: 900 !important; font-size: 13px; }
33
  </style>
34
  """, unsafe_allow_html=True)
35
 
 
45
  df = load_data()
46
  all_models = df['model name'].unique().tolist()
47
 
 
48
  with st.sidebar:
49
  st.image("https://upload.wikimedia.org/wikipedia/commons/thumb/0/08/Decathlon_Logo.svg/1280px-Decathlon_Logo.svg.png", width=200)
50
+ st.title("βš™οΈ SETTINGS")
51
  x_input = st.slider("🫱 Lever Effort [N]", 40, 200, 100)
52
  st.markdown("---")
53
  selected_models = st.multiselect("Select Models to Display", options=all_models, default=all_models[:2])
 
62
  elif norm_type == "MTB": n_dry, n_wet = 425, 280
63
  elif norm_type == "Racing": n_dry, n_wet = 425, 260
64
 
 
65
  with st.expander("πŸ” Display Options"):
66
  show_loss = st.checkbox("Show Wet Loss Analysis", value=True)
67
  enable_comparison = st.checkbox("Enable Reference Comparison", value=True)
 
69
  condition_view = st.radio("Conditions to display", ["Both", "Dry only", "Wet only"], index=0)
70
 
71
  # --- DIAGNOSTIC HEADER ---
72
+ if x_input < 70: label, color_alert = "❄️ LIGHT BRAKING", "#a1c4fd"
73
+ elif 70 <= x_input <= 110: label, color_alert = "βš–οΈ MODERATE BRAKING", "#ffdb58"
74
+ else: label, color_alert = "πŸ”₯ POWERFUL BRAKING", "#ff4b4b"
 
 
 
75
 
76
  st.markdown(f"""
77
+ <div style="background-color:{color_alert}; padding:10px; border-radius:8px; text-align:center; border: 3px solid #000000; margin-bottom: 15px;">
78
+ <span style="color:#000000 !important; font-weight:900; font-size:16px;">{label} | Effort: {round(float(x_input), 1)} N</span>
79
  </div>
80
  """, unsafe_allow_html=True)
81
 
 
83
  filtered_df = df[df['model name'].isin(selected_models)]
84
  fig = go.Figure()
85
  x_range = np.linspace(40, 200, 150)
86
+ colors = ['#0082C3', '#E63312', '#000000', '#00A14B', '#FFD200']
87
 
88
  row_ref = df[df['model name'] == ref_model].iloc[0]
89
  ref_dry_val = row_ref['dry a'] * x_input + row_ref['dry b']
 
92
  comparison_results = []
93
  for i, (index, row) in enumerate(filtered_df.iterrows()):
94
  color = colors[i % len(colors)]
95
+ y_d = row['dry a'] * x_input + row['dry b']
96
+ y_w = row['wet a'] * x_input + row['wet b']
97
+ comparison_results.append({"name": row['model name'], "dry": y_d, "wet": y_w, "row": row})
98
 
99
  if condition_view in ["Both", "Dry only"]:
100
+ 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>"))
101
  if n_dry > 0:
102
+ xt = (n_dry - row['dry b']) / row['dry a']
103
+ 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="black", size=12), textposition="top center", marker=dict(color=color, size=10, symbol='x'), showlegend=False))
 
104
 
105
  if condition_view in ["Both", "Wet only"]:
106
+ 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'), hovertemplate=f"<b>{row['model name']}</b><br>Perf: %{{y:.1f}} N<extra></extra>"))
107
  if n_wet > 0:
108
+ xtw = (n_wet - row['wet b']) / row['wet a']
109
+ 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="black", size=12), textposition="bottom center", marker=dict(color=color, size=10, symbol='circle-open'), showlegend=False))
 
110
 
111
+ # Lignes de normes
112
  if n_dry > 0 and (condition_view in ["Both", "Dry only"]):
113
+ fig.add_hline(y=n_dry, line_width=3, line_color="#000000", annotation_text=f"<b>Norm Dry: {n_dry}N</b>", annotation_font=dict(color="black", size=12))
114
  if n_wet > 0 and (condition_view in ["Both", "Wet only"]):
115
+ fig.add_hline(y=n_wet, line_width=3, line_dash="dot", line_color="#000000", annotation_text=f"<b>Norm Wet: {n_wet}N</b>", annotation_font=dict(color="black", size=12))
116
 
117
+ fig.add_vline(x=x_input, line_width=2, line_dash="dash", line_color="#000000")
118
+
119
+ # --- NOIR TOTAL SUR LE GRAPHIQUE ---
120
+ fig.update_layout(
121
+ height=480,
122
+ xaxis=dict(title="Lever Effort [N]", color="#000000", linecolor="#000000", linewidth=3, tickfont=dict(color="#000000", size=13, bold=True), gridcolor="#E0E0E0"),
123
+ yaxis=dict(title="Performance [N]", color="#000000", linecolor="#000000", linewidth=3, tickfont=dict(color="#000000", size=13, bold=True), gridcolor="#E0E0E0"),
124
+ font=dict(color="#000000", size=12),
125
+ plot_bgcolor='white', paper_bgcolor='white',
126
+ hovermode="x unified",
127
+ legend=dict(font=dict(color="#000000", size=12, bold=True), bordercolor="#000000", borderwidth=2, bgcolor="white")
128
+ )
129
  st.plotly_chart(fig, use_container_width=True)
130
 
131
  # --- ANALYSIS DASHBOARD (BOTTOM) ---
132
+ st.markdown(f"<p style='color:black; font-weight:900; font-size:16px;'>πŸ“Š Performance Analysis [N] | Ref: {ref_model}</p>", unsafe_allow_html=True)
133
 
134
  if not filtered_df.empty:
135
  cols = st.columns(len(comparison_results))
136
  for i, res in enumerate(comparison_results):
137
  with cols[i]:
138
  is_ref = (res['name'] == ref_model)
139
+ st.markdown(f"<p style='font-size:14px; font-weight:900; color:black; margin-bottom:5px; text-decoration: underline;'>{res['name']} {'⭐' if is_ref else ''}</p>", unsafe_allow_html=True)
140
 
 
141
  if condition_view in ["Both", "Dry only"]:
142
+ dv = round(res['dry'], 1)
143
  if enable_comparison and not is_ref:
144
+ diff = dv - round(ref_dry_val, 1)
145
  pct = (diff / ref_dry_val * 100) if ref_dry_val != 0 else 0
146
+ st.metric("Dry Perf.", f"{dv} N", f"{diff:+.1f} N ({pct:+.1f}%) Vs Ref.")
147
+ else: st.metric("Dry Perf.", f"{dv} N")
 
148
 
 
149
  if n_dry > 0:
150
+ xt = (n_dry - res['row']['dry b']) / res['row']['dry a']
151
+ 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)
152
+ else: st.markdown(f"<div class='check-green'>βœ… Conforme Sec ({round(xt,1)}N)</div>", unsafe_allow_html=True)
 
 
153
 
 
154
  if condition_view in ["Both", "Wet only"]:
155
+ wv = round(res['wet'], 1)
156
  if enable_comparison and not is_ref:
157
+ diffw = wv - round(ref_wet_val, 1)
158
+ pctw = (diffw / ref_wet_val * 100) if ref_wet_val != 0 else 0
159
+ st.metric("Wet Perf.", f"{wv} N", f"{diffw:+.1f} N ({pctw:+.1f}%) Vs Ref.")
160
+ else: st.metric("Wet Perf.", f"{wv} N")
 
161
 
 
162
  if n_wet > 0:
163
+ xtw = (n_wet - res['row']['wet b']) / res['row']['wet a']
164
+ 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)
165
+ else: st.markdown(f"<div class='check-green'>βœ… Conforme Humide ({round(xtw,1)}N)</div>", unsafe_allow_html=True)
 
 
166
 
 
167
  if show_loss and condition_view == "Both":
168
  loss_pct = ((res['dry'] - res['wet']) / res['dry'] * 100) if res['dry'] != 0 else 0
169
  st.metric("Efficiency Loss", f"-{round(loss_pct, 1)}%", f"{round(res['wet']-res['dry'], 1)} N vs Dry", delta_color="inverse")