NeuralGearheads commited on
Commit
4c1c382
Β·
verified Β·
1 Parent(s): b144009

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +307 -136
src/streamlit_app.py CHANGED
@@ -5,28 +5,88 @@ from sklearn.preprocessing import StandardScaler
5
  from sklearn.neighbors import KNeighborsRegressor
6
 
7
  # ---------------------------
8
- # Load / generate dataset (NO CHANGE)
9
  # ---------------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
- @st.cache_resource
12
- def generate_and_train_model(n=400):
13
- """Generates dataset and trains the model, cached for performance."""
 
14
  np.random.seed(42)
15
  data = []
16
-
17
  for _ in range(n):
18
  engine = np.random.choice([1.6, 2.0, 2.5, 3.0, 3.5, 5.0])
19
  cyl = np.random.choice([4, 6, 8])
20
  base_hp = int(engine * cyl * np.random.uniform(18, 22))
21
-
22
  intake = np.random.choice([0, 1, 2]) # stock/CAI/perf
23
  exhaust = np.random.choice([0, 1, 2]) # stock/catback/straight
24
  induction = np.random.choice([0, 1, 2]) # none/turbo/super
25
  fuel = np.random.choice([0, 1, 2, 3]) # 87/91/93/E85
26
  tune = np.random.choice([0, 1, 2]) # none/mild/aggressive
27
  altitude = np.random.uniform(0, 2000)
28
-
29
- # Synthetic HP gain logic
30
  hp_gain = (
31
  intake * np.random.uniform(3, 10) +
32
  exhaust * np.random.uniform(5, 20) +
@@ -36,153 +96,264 @@ def generate_and_train_model(n=400):
36
  altitude * 0.01 +
37
  np.random.uniform(-3, 3)
38
  )
39
-
40
  data.append([engine, cyl, base_hp, intake, exhaust, induction, fuel, tune, altitude, hp_gain])
41
-
42
  columns = ["engine", "cyl", "base_hp", "intake", "exhaust", "induction", "fuel", "tune", "altitude", "hp_gain"]
43
- df = pd.DataFrame(data, columns=columns)
44
-
45
- # Train model
46
- X = df.drop("hp_gain", axis=1)
47
- y = df["hp_gain"]
48
-
49
- scaler = StandardScaler()
50
- X_scaled = scaler.fit_transform(X)
51
-
52
- model = KNeighborsRegressor(n_neighbors=5, weights='distance')
53
- model.fit(X_scaled, y)
54
-
55
- return scaler, model
56
-
57
- scaler, model = generate_and_train_model()
58
-
59
- # ---------------------------
60
- # Streamlit UI (REVISED)
61
- # ---------------------------
62
-
63
- # Use a wide layout and set an exciting title/icon
64
- st.set_page_config(layout="wide")
65
- st.title("πŸš€ Turbo-Tuner: Performance Estimator")
66
- st.markdown("### Predict the **horsepower gain** from your engine modifications using K-NN Regression.")
67
-
68
- st.divider()
69
-
70
- # --- INPUT SECTION ---
71
-
72
- # Group base car specs and modification specs using columns and containers
73
- col_base, col_mods = st.columns(2)
74
-
75
- # Base Car Specifications (Column 1)
76
- with col_base:
77
- with st.container(border=True):
78
- st.subheader("🏎️ Base Car Specs")
79
- st.markdown("---")
80
-
81
- # Use columns for a compact layout
82
- col_base_1, col_base_2 = st.columns(2)
83
-
84
- with col_base_1:
85
- engine = st.selectbox("Engine Displacement (L)", [1.6, 2.0, 2.5, 3.0, 3.5, 5.0], help="Volume of the engine cylinders.")
86
-
87
- with col_base_2:
88
- cyl = st.selectbox("Cylinders", [4, 6, 8], help="Number of engine cylinders.")
89
-
90
- base_hp = st.number_input(
91
- "Base Horsepower (HP)",
92
- min_value=80, max_value=700,
93
- value=200,
94
- step=10,
95
- help="Your car's factory horsepower rating."
96
- )
97
-
98
- altitude = st.slider(
99
- "Altitude (meters above sea level)",
100
- 0, 2000, 200,
101
- help="Higher altitude typically reduces power."
102
- )
103
 
104
- # Modification Specs (Column 2)
105
- with col_mods:
106
- with st.container(border=True):
107
- st.subheader("πŸ”§ Planned Modifications")
108
- st.markdown("---")
109
-
110
- # Use two columns for modification parts
111
- mod_col_1, mod_col_2 = st.columns(2)
112
-
113
- with mod_col_1:
114
- intake = st.selectbox("Intake System", ["Stock", "Cold Air", "Performance"])
115
- exhaust = st.selectbox("Exhaust System", ["Stock", "Cat-back", "Straight Pipe"])
116
- induction = st.selectbox("Forced Induction", ["None", "Turbo", "Supercharger"])
117
-
118
- with mod_col_2:
119
- fuel = st.selectbox("Fuel Octane/Type", ["87", "91", "93", "E85"])
120
- tune = st.selectbox("ECU Tune Level", ["None", "Mild", "Aggressive"])
121
- # Placeholder to balance the layout
122
- st.write(" ")
123
-
124
- # --- PREDICTION LOGIC ---
125
-
126
- # Map categorical to numeric
 
 
 
 
 
 
 
 
 
 
 
127
  intake_map = {"Stock":0, "Cold Air":1, "Performance":2}
128
  exhaust_map = {"Stock":0, "Cat-back":1, "Straight Pipe":2}
129
- induction_map = {"None":0, "Turbo":1, "Supercharger":2}
 
130
  fuel_map = {"87":0, "91":1, "93":2, "E85":3}
131
  tune_map = {"None":0, "Mild":1, "Aggressive":2}
132
 
133
- input_data = np.array([[
134
- engine,
135
  cyl,
136
  base_hp,
137
- intake_map[intake],
138
- exhaust_map[exhaust],
139
- induction_map[induction],
140
- fuel_map[fuel],
141
- tune_map[tune],
142
  altitude
143
  ]])
144
 
145
- # Ensure input_data is a DataFrame for feature names if needed, but array works for scaling
146
- # X.columns is not accessible here, so use the trained scaler directly
147
- input_scaled = scaler.transform(input_data)
148
- pred = model.predict(input_scaled)[0]
149
 
150
- new_hp = base_hp + pred
151
-
152
- # --- OUTPUT SECTION ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
 
154
- st.divider()
155
- st.subheader("🎯 Prediction Results")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
 
157
- # Use three columns for the key metrics for impact
158
- metric_col_1, metric_col_2, metric_col_3 = st.columns(3)
159
 
160
- # Display estimated HP gain
161
- with metric_col_1:
162
- st.metric(
163
- label="πŸ”₯ Estimated HP Gain",
164
- value=f"{pred:.1f} HP",
165
- delta=f"{(pred / base_hp * 100):.1f}% over base",
166
- delta_color="normal" if pred > 0 else "inverse" # Green if gain, Red if loss
167
- )
168
 
169
- # Display new total HP
170
- with metric_col_2:
171
- st.metric(
172
- label="🏁 New Total Horsepower",
173
- value=f"{new_hp:.1f} HP"
174
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
 
176
- # Display a comparison chart
177
- with metric_col_3:
178
- # A small area chart can be more dynamic than a bar chart for this comparison
179
- chart_data = pd.DataFrame(
180
- {"Horsepower": [base_hp, new_hp]},
181
- index=["Base HP", "Modified HP"]
182
- )
183
- st.area_chart(chart_data)
 
184
 
 
 
 
 
 
 
 
 
 
 
 
185
 
186
- st.success(f"Prediction complete! Your **{base_hp} HP** car is estimated to be **{new_hp:.1f} HP** with these modifications.")
187
- st.caption("Disclaimer: This is a synthetic prediction based on a simplified model and generated data. Use for fun!")
188
 
 
5
  from sklearn.neighbors import KNeighborsRegressor
6
 
7
  # ---------------------------
8
+ # Page config & macOS-like styling
9
  # ---------------------------
10
+ st.set_page_config(
11
+ page_title="Car Mod Performance Estimator β€” macOS Edition",
12
+ page_icon="🚘",
13
+ layout="wide"
14
+ )
15
+
16
+ st.markdown(
17
+ """
18
+ <style>
19
+ /* macOS-like system font and gentle background */
20
+ html, body, #root, .main {
21
+ font-family: -apple-system, "SF Pro Text", "Segoe UI", Roboto, "Helvetica Neue", Arial;
22
+ background: linear-gradient(180deg, #f7f8fa 0%, #eef1f6 100%);
23
+ color: #0b1220;
24
+ }
25
+
26
+ /* Frosted panels */
27
+ .frost {
28
+ background: rgba(255,255,255,0.72);
29
+ border-radius: 14px;
30
+ padding: 18px;
31
+ box-shadow: 0 8px 24px rgba(14, 21, 47, 0.06);
32
+ border: 1px solid rgba(13, 22, 39, 0.04);
33
+ }
34
+
35
+ /* Headline */
36
+ .headline {
37
+ display:flex;
38
+ align-items:center;
39
+ gap:12px;
40
+ margin-bottom:6px;
41
+ }
42
+ .headline h1 { margin: 0; font-size: 1.6rem; }
43
+ .headline p { margin: 0; color:#6b7280; font-size:0.95rem; }
44
+
45
+ /* Small chips */
46
+ .chip {
47
+ display:inline-block;
48
+ padding:6px 10px;
49
+ margin:4px 6px 4px 0;
50
+ border-radius:999px;
51
+ background: rgba(14,165,233,0.10);
52
+ color:#0369a1;
53
+ border: 1px solid rgba(14,165,233,0.18);
54
+ font-size:0.85rem;
55
+ }
56
+
57
+ /* subtle footer text */
58
+ .muted { color:#6b7280; font-size:0.9rem; }
59
+
60
+ /* metric cards adapt */
61
+ .metric-card {
62
+ background: linear-gradient(180deg, rgba(255,255,255,0.85), rgba(250,250,250,0.75));
63
+ border-radius: 12px;
64
+ padding: 12px;
65
+ box-shadow: 0 6px 20px rgba(14,21,47,0.04);
66
+ border: 1px solid rgba(13,22,39,0.03);
67
+ }
68
+
69
+ </style>
70
+ """,
71
+ unsafe_allow_html=True
72
+ )
73
 
74
+ # ---------------------------
75
+ # Synthetic data & model (unchanged logic)
76
+ # ---------------------------
77
+ def generate_dataset(n=400):
78
  np.random.seed(42)
79
  data = []
 
80
  for _ in range(n):
81
  engine = np.random.choice([1.6, 2.0, 2.5, 3.0, 3.5, 5.0])
82
  cyl = np.random.choice([4, 6, 8])
83
  base_hp = int(engine * cyl * np.random.uniform(18, 22))
 
84
  intake = np.random.choice([0, 1, 2]) # stock/CAI/perf
85
  exhaust = np.random.choice([0, 1, 2]) # stock/catback/straight
86
  induction = np.random.choice([0, 1, 2]) # none/turbo/super
87
  fuel = np.random.choice([0, 1, 2, 3]) # 87/91/93/E85
88
  tune = np.random.choice([0, 1, 2]) # none/mild/aggressive
89
  altitude = np.random.uniform(0, 2000)
 
 
90
  hp_gain = (
91
  intake * np.random.uniform(3, 10) +
92
  exhaust * np.random.uniform(5, 20) +
 
96
  altitude * 0.01 +
97
  np.random.uniform(-3, 3)
98
  )
 
99
  data.append([engine, cyl, base_hp, intake, exhaust, induction, fuel, tune, altitude, hp_gain])
 
100
  columns = ["engine", "cyl", "base_hp", "intake", "exhaust", "induction", "fuel", "tune", "altitude", "hp_gain"]
101
+ return pd.DataFrame(data, columns=columns)
102
+
103
+ df = generate_dataset()
104
+
105
+ X = df.drop("hp_gain", axis=1)
106
+ y = df["hp_gain"]
107
+ scaler = StandardScaler()
108
+ X_scaled = scaler.fit_transform(X)
109
+
110
+ model = KNeighborsRegressor(n_neighbors=5, weights='distance')
111
+ model.fit(X_scaled, y)
112
+
113
+ # ---------------------------
114
+ # UI - Header
115
+ # ---------------------------
116
+ st.markdown(
117
+ """
118
+ <div class="headline">
119
+ <div style="font-size:1.6rem;">🚘</div>
120
+ <div>
121
+ <h1>Car Mod Performance β€” macOS UI</h1>
122
+ <p>Interactive estimator with extra tuning parameters β€” realistic, lightweight, and stylish.</p>
123
+ </div>
124
+ </div>
125
+ """,
126
+ unsafe_allow_html=True
127
+ )
128
+
129
+ # ---------------------------
130
+ # Main layout: left (controls) and right (dashboard)
131
+ # ---------------------------
132
+ left, right = st.columns([1.05, 1])
133
+
134
+ with left:
135
+ st.markdown('<div class="frost">', unsafe_allow_html=True)
136
+ st.subheader("πŸ”§ Build & Tune")
137
+ col1, col2 = st.columns(2)
138
+
139
+ # Engine choices: displacement + configuration (I4..V12)
140
+ engine_disp_options = [0.8,1.0,1.2,1.4,1.6,1.8,2.0,2.2,2.4,2.5,2.8,3.0,3.2,3.5,4.0,4.4,5.0,6.0,8.0]
141
+ engine_disp = col1.selectbox("Engine Displacement (L)", engine_disp_options, index=engine_disp_options.index(2.0) if 2.0 in engine_disp_options else 0)
142
+
143
+ engine_config = col2.selectbox("Engine Layout", ["I4","I6","V6","V8","V10","V12"], index=0)
144
+ # Map layout -> cylinders
145
+ layout_to_cyl = {"I4":4,"I6":6,"V6":6,"V8":8,"V10":10,"V12":12}
146
+ cyl = layout_to_cyl[engine_config]
147
+
148
+ base_hp = st.number_input("Base Horsepower (stock)", min_value=60, max_value=1200, value=int(max(90, round(engine_disp * cyl * 20))), step=1)
149
+
150
+ # Vehicle weight & perception
151
+ weight_kg = st.number_input("Vehicle Weight (kg)", min_value=700, max_value=4000, value=1500)
152
+ weight_reduction = st.slider("Weight Reduction (%) β€” (mods / lightening)", 0, 40, 0)
 
 
 
 
 
 
 
 
153
 
154
+ st.markdown("---")
155
+ st.markdown("#### 🧩 Bolt-ons & Induction")
156
+
157
+ intake = st.selectbox("Intake", ["Stock", "Cold Air", "Performance"])
158
+ exhaust = st.selectbox("Exhaust", ["Stock", "Cat-back", "Straight Pipe"])
159
+ exhaust_dia = st.slider("Exhaust Diameter (mm)", 40, 120, 60)
160
+
161
+ induction = st.selectbox("Forced Induction", ["None", "Turbo", "Twin-Turbo", "Supercharger", "Twincharged"])
162
+ # boost only relevant if induction present
163
+ boost_psi = st.slider("Target Boost (psi) β€” (only if turbo/super)", 0, 40, 8)
164
+
165
+ turbo_size = st.selectbox("Turbo Size (mm) β€” (if applicable)", ["N/A","Shr. 45-50","Small 50-60","Medium 60-70","Large 70+"])
166
+ intercooler = st.selectbox("Intercooler", ["None","Air-to-Air","Air-to-Water"])
167
+ meth = st.checkbox("Methanol Injection (Wet Kit)", value=False)
168
+
169
+ st.markdown("---")
170
+ st.markdown("#### βš™οΈ Internal & ECU")
171
+ cam = st.selectbox("Cam Profile", ["Stock", "Road", "Race"])
172
+ headers = st.selectbox("Headers", ["Stock", "Performance"])
173
+ intake_manifold = st.selectbox("Intake Manifold", ["Stock", "High-flow"])
174
+ tune = st.selectbox("ECU Tune Level", ["None", "Mild", "Aggressive"])
175
+
176
+ st.markdown("---")
177
+ st.markdown("#### β›½ Fuel & Environment")
178
+ fuel = st.selectbox("Fuel Octane / Type", ["87","91","93","E85"])
179
+ altitude = st.slider("Altitude (meters)", 0, 3000, 200)
180
+
181
+ st.markdown("---")
182
+ st.markdown('<div class="muted">Tip: The ML model uses a synthetic dataset β€” results are approximate and for educational/estimation use only.</div>', unsafe_allow_html=True)
183
+ st.markdown('</div>', unsafe_allow_html=True)
184
+
185
+ # ---------------------------
186
+ # Map categorical to numeric (for model input)
187
+ # ---------------------------
188
  intake_map = {"Stock":0, "Cold Air":1, "Performance":2}
189
  exhaust_map = {"Stock":0, "Cat-back":1, "Straight Pipe":2}
190
+ # collapse twin-turbo and twincharged to turbo/supercharger categories for model
191
+ induction_model_map = {"None":0, "Turbo":1, "Twin-Turbo":1, "Supercharger":2, "Twincharged":2}
192
  fuel_map = {"87":0, "91":1, "93":2, "E85":3}
193
  tune_map = {"None":0, "Mild":1, "Aggressive":2}
194
 
195
+ input_for_model = np.array([[
196
+ engine_disp,
197
  cyl,
198
  base_hp,
199
+ intake_map.get(intake,0),
200
+ exhaust_map.get(exhaust,0),
201
+ induction_model_map.get(induction,0),
202
+ fuel_map.get(fuel,0),
203
+ tune_map.get(tune,0),
204
  altitude
205
  ]])
206
 
207
+ # Predict base gain from the trained model
208
+ input_scaled = scaler.transform(input_for_model)
209
+ pred_base = float(model.predict(input_scaled)[0])
 
210
 
211
+ # ---------------------------
212
+ # Heuristic extra gains from new advanced params
213
+ # (We add these on top of model prediction to reflect advanced bolt-ons)
214
+ # ---------------------------
215
+ # cam, headers, intake manifold contributions
216
+ cam_gain_map = {"Stock":0.0, "Road":5.0, "Race":12.0}
217
+ headers_gain_map = {"Stock":0.0, "Performance":6.0}
218
+ intake_manifold_gain = {"Stock":0.0, "High-flow":4.0}
219
+ intercooler_gain_map = {"None":0.0, "Air-to-Air":4.0, "Air-to-Water":6.5}
220
+ turbo_size_map = {"N/A":0.0, "Shr. 45-50":2.5, "Small 50-60":6.0, "Medium 60-70":12.0, "Large 70+":20.0}
221
+
222
+ cam_gain = cam_gain_map.get(cam, 0.0)
223
+ headers_gain = headers_gain_map.get(headers, 0.0)
224
+ intake_man_gain = intake_manifold_gain.get(intake_manifold, 0.0)
225
+ intercooler_gain = intercooler_gain_map.get(intercooler, 0.0)
226
+ turbo_size_gain = turbo_size_map.get(turbo_size, 0.0)
227
+
228
+ # boost contribution: if induction present, boost * factor; twin turbo gives more but is captured by induction choice
229
+ if induction in ["Turbo", "Twin-Turbo"]:
230
+ boost_gain = boost_psi * 1.9 # psi-to-hp rough factor for turbo setups (heuristic)
231
+ elif induction in ["Supercharger", "Twincharged"]:
232
+ boost_gain = boost_psi * 1.4 # superchargers typically have different curve
233
+ else:
234
+ boost_gain = 0.0
235
+
236
+ # methanol injection
237
+ meth_gain = 10.0 if meth else 0.0
238
+
239
+ # exhaust diameter small effect (larger diameter -> small gain if engine can flow)
240
+ exhaust_dia_gain = max(0.0, (exhaust_dia - 55) * 0.08)
241
+
242
+ # combined extra heuristic
243
+ extra_gain = (
244
+ cam_gain +
245
+ headers_gain +
246
+ intake_man_gain +
247
+ intercooler_gain +
248
+ turbo_size_gain +
249
+ boost_gain * 0.9 + # scale down a bit to avoid massive overestimates
250
+ meth_gain +
251
+ exhaust_dia_gain
252
+ )
253
+
254
+ # final predicted gain: model base + heuristic extra
255
+ pred_total_gain = pred_base + extra_gain
256
+ new_hp = base_hp + pred_total_gain
257
+
258
+ # account for realistic minimums
259
+ pred_total_gain = max(pred_total_gain, -5.0) # avoid negative crazy values
260
+ new_hp = max(new_hp, 30.0)
261
+
262
+ # power-to-weight (hp per ton)
263
+ effective_weight = weight_kg * (1 - weight_reduction / 100.0)
264
+ hp_per_ton = new_hp / (effective_weight / 1000.0)
265
 
266
+ # ---------------------------
267
+ # Right panel: Dashboard (macOS style cards)
268
+ # ---------------------------
269
+ with right:
270
+ st.markdown('<div class="frost">', unsafe_allow_html=True)
271
+ st.subheader("πŸ“Š Performance Dashboard")
272
+
273
+ # Top metrics
274
+ k1, k2, k3 = st.columns(3)
275
+ k1.metric("Estimated HP Gain", f"{pred_total_gain:.1f} HP")
276
+ k2.metric("New Estimated Horsepower", f"{new_hp:.1f} HP")
277
+ k3.metric("HP / Ton", f"{hp_per_ton:.1f}")
278
+
279
+ # Build quick summary chips
280
+ st.markdown(
281
+ f"""
282
+ <div style="margin-top:10px;">
283
+ <span class="chip">Engine: {engine_disp}L β€’ {engine_config}</span>
284
+ <span class="chip">Intake: {intake}</span>
285
+ <span class="chip">Exhaust: {exhaust} ({exhaust_dia}mm)</span>
286
+ <span class="chip">Induction: {induction} β€’ Boost: {boost_psi}psi</span>
287
+ <span class="chip">Tune: {tune}</span>
288
+ <span class="chip">Fuel: {fuel}</span>
289
+ </div>
290
+ """,
291
+ unsafe_allow_html=True
292
+ )
293
 
294
+ st.markdown("---")
 
295
 
296
+ # small two-column charts & info
297
+ left_panel, right_panel = st.columns([1, 1])
 
 
 
 
 
 
298
 
299
+ with left_panel:
300
+ st.markdown('<div class="metric-card">', unsafe_allow_html=True)
301
+ st.write("Power Curve Snapshot")
302
+ st.bar_chart(
303
+ pd.DataFrame(
304
+ {"HP": [base_hp, base_hp + pred_base, new_hp]},
305
+ index=["Stock", "Model Gain", "Final"]
306
+ )
307
+ )
308
+ st.markdown('</div>', unsafe_allow_html=True)
309
+
310
+ with right_panel:
311
+ st.markdown('<div class="metric-card">', unsafe_allow_html=True)
312
+ st.write("Tuning Contribution Breakdown")
313
+ breakdown = pd.DataFrame({
314
+ "component": ["Model base", "Cam", "Headers", "Intake Manifold", "Intercooler", "Turbo Size", "Boost", "Meth", "Exhaust Dia"],
315
+ "hp": [pred_base, cam_gain, headers_gain, intake_man_gain, intercooler_gain, turbo_size_gain, boost_gain * 0.9, meth_gain, exhaust_dia_gain]
316
+ })
317
+ st.dataframe(breakdown.style.format("{:.1f}").hide_index(), height=220)
318
+ st.markdown('</div>', unsafe_allow_html=True)
319
+
320
+ st.markdown("---")
321
+ # performance badge description
322
+ perf_text = "Balanced cruiser"
323
+ if pred_total_gain < 15:
324
+ perf_text = "Mild improvement β€” street friendly"
325
+ elif pred_total_gain < 50:
326
+ perf_text = "Noticeable power β€” spirited driving"
327
+ else:
328
+ perf_text = "Serious power β€” track-capable build"
329
+
330
+ st.markdown(f"### ⚑ Build verdict: **{perf_text}**")
331
+ st.write("Power-to-weight and HP gain are quick indicators β€” actual drivability depends on gearing, cooling, and reliability.")
332
+
333
+ st.markdown("<div class='muted'>Tip: This estimator combines a synthetic ML model plus simple heuristics for extra bolt-ons. For precise dyno numbers consult a professional tuner.</div>", unsafe_allow_html=True)
334
+
335
+ st.markdown("</div>", unsafe_allow_html=True)
336
 
337
+ # ---------------------------
338
+ # Optional: show dataset / debug
339
+ # ---------------------------
340
+ with st.expander("πŸ”Ž Peek training data & model info"):
341
+ st.write("Sample synthetic dataset (used to train the toy estimator):")
342
+ st.dataframe(df.head(10))
343
+ st.write("- Model: KNeighborsRegressor (distance weighted)")
344
+ st.write("- Additional bolt-on gains computed with lightweight heuristics")
345
+ st.write("- Use this for estimation and learning, not as a dyno replacement.")
346
 
347
+ # ---------------------------
348
+ # Footer
349
+ # ---------------------------
350
+ st.markdown(
351
+ """
352
+ <div style="margin-top:14px; text-align:center;">
353
+ <span class="muted">Made for learning β€” treat numbers as estimates. Enjoy tuning! 🍏</span>
354
+ </div>
355
+ """,
356
+ unsafe_allow_html=True
357
+ )
358
 
 
 
359