NeuralGearheads commited on
Commit
4f6b0a9
Β·
verified Β·
1 Parent(s): 7173a53

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +229 -339
src/streamlit_app.py CHANGED
@@ -4,360 +4,250 @@ import numpy as np
4
  from sklearn.preprocessing import StandardScaler
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) +
93
- induction * np.random.uniform(25, 100) +
94
- tune * np.random.uniform(10, 35) +
95
- fuel * np.random.uniform(2, 8) -
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(
318
- breakdown.style.format("{:.1f}"),
319
- height=220,
320
- use_container_width=True
321
- )
322
-
323
-
324
- st.markdown("---")
325
- # performance badge description
326
- perf_text = "Balanced cruiser"
327
- if pred_total_gain < 15:
328
- perf_text = "Mild improvement β€” street friendly"
329
- elif pred_total_gain < 50:
330
- perf_text = "Noticeable power β€” spirited driving"
331
- else:
332
- perf_text = "Serious power β€” track-capable build"
333
-
334
- st.markdown(f"### ⚑ Build verdict: **{perf_text}**")
335
- st.write("Power-to-weight and HP gain are quick indicators β€” actual drivability depends on gearing, cooling, and reliability.")
336
-
337
- 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)
338
-
339
- st.markdown("</div>", unsafe_allow_html=True)
340
-
341
- # ---------------------------
342
- # Optional: show dataset / debug
343
- # ---------------------------
344
- with st.expander("πŸ”Ž Peek training data & model info"):
345
- st.write("Sample synthetic dataset (used to train the toy estimator):")
346
- st.dataframe(df.head(10))
347
- st.write("- Model: KNeighborsRegressor (distance weighted)")
348
- st.write("- Additional bolt-on gains computed with lightweight heuristics")
349
- st.write("- Use this for estimation and learning, not as a dyno replacement.")
350
-
351
- # ---------------------------
352
- # Footer
353
- # ---------------------------
354
- st.markdown(
355
- """
356
- <div style="margin-top:14px; text-align:center;">
357
- <span class="muted">Made for learning β€” treat numbers as estimates. Enjoy tuning! 🍏</span>
358
  </div>
359
- """,
360
- unsafe_allow_html=True
361
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
362
 
363
 
 
4
  from sklearn.preprocessing import StandardScaler
5
  from sklearn.neighbors import KNeighborsRegressor
6
 
7
+ # ---------------------------------------------------------
8
+ # 1. macOS STYLING (CSS Injection)
9
+ # ---------------------------------------------------------
10
+ st.set_page_config(page_title="Mac-Mod Tuner", page_icon="πŸ–₯️", layout="wide")
11
+
12
+ st.markdown("""
13
+ <style>
14
+ /* Main Background - Apple Light Grey */
15
+ .stApp {
16
+ background-color: #f5f5f7;
17
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
 
 
 
 
 
 
18
  }
19
+
20
+ /* Sidebar Styling */
21
+ section[data-testid="stSidebar"] {
22
+ background-color: #e8e8ed; /* Slightly darker grey */
23
+ border-right: 1px solid #d1d1d6;
 
 
 
24
  }
25
+
26
+ /* Card/Container Styling (The "Window" look) */
27
+ div[data-testid="stVerticalBlock"] > div[style*="background-color"] {
28
+ background-color: white;
29
+ border-radius: 18px;
30
+ padding: 20px;
31
+ box-shadow: 0 4px 20px rgba(0,0,0,0.05);
32
+ border: 1px solid #e5e5ea;
33
  }
 
 
34
 
35
+ /* Input Widgets - Rounded pills */
36
+ .stSelectbox > div > div {
37
+ border-radius: 12px;
38
+ border: 1px solid #d1d1d6;
 
 
 
 
 
 
39
  }
40
+ .stNumberInput > div > div > input {
 
 
 
 
 
 
41
  border-radius: 12px;
 
 
 
42
  }
43
+
44
+ /* Metrics Styling */
45
+ div[data-testid="stMetricValue"] {
46
+ font-weight: 600;
47
+ color: #1d1d1f;
48
+ }
49
+
50
+ /* Headers */
51
+ h1, h2, h3 {
52
+ color: #1d1d1f;
53
+ font-weight: 700;
54
+ letter-spacing: -0.5px;
55
+ }
56
+
57
+ /* Custom "Traffic Lights" for visual flair */
58
+ .traffic-lights {
59
+ display: flex;
60
+ gap: 8px;
61
+ margin-bottom: 20px;
62
+ }
63
+ .dot { width: 12px; height: 12px; border-radius: 50%; }
64
+ .red { background-color: #ff5f57; border: 1px solid #e0443e; }
65
+ .yellow { background-color: #febc2e; border: 1px solid #d89e24; }
66
+ .green { background-color: #28c840; border: 1px solid #1aab29; }
67
+ </style>
68
+ """, unsafe_allow_html=True)
69
+
70
+ # ---------------------------------------------------------
71
+ # 2. DATA & MODEL (Expanded for V10/V12 & Nitrous)
72
+ # ---------------------------------------------------------
73
+
74
+ @st.cache_resource
75
+ def train_model():
76
  np.random.seed(42)
77
+ n = 1000 # More data for better accuracy with V12s
78
  data = []
79
+
80
  for _ in range(n):
81
+ # Expanded Engine Options
82
+ engine = np.random.choice([1.6, 2.0, 2.4, 3.0, 3.8, 4.0, 5.0, 5.2, 6.0, 6.5])
83
+ cyl = np.random.choice([4, 6, 8, 10, 12])
84
+
85
+ # Base HP logic (roughly 80-120 hp per liter depending on tech)
86
+ base_hp = int(engine * np.random.uniform(60, 110))
87
+
88
+ # Mods
89
+ intake = np.random.choice([0, 1, 2]) # Stock/Sport/Race
90
+ exhaust = np.random.choice([0, 1, 2, 3]) # Stock/Catback/Headers/Straight
91
+ induction = np.random.choice([0, 1, 2, 3]) # None/Turbo/Twin-Turbo/Super
92
+ cams = np.random.choice([0, 1, 2]) # Stock/Street/Track
93
+ nitrous = np.random.choice([0, 1]) # No/Yes (50 shot equivalent)
94
+ fuel = np.random.choice([0, 1, 2, 3]) # 87/91/93/E85
95
+ tune = np.random.choice([0, 1, 2]) # None/Stage 1/Stage 2
96
+
97
+ # Gain Logic
98
+ gain = (
99
+ (intake * 3) +
100
+ (exhaust * 5) +
101
+ (induction * (base_hp * 0.35)) + # Forced induction is % based
102
+ (cams * (base_hp * 0.10)) +
103
+ (nitrous * 50) +
104
+ (tune * (base_hp * 0.08)) +
105
+ (fuel * 4) +
106
+ np.random.uniform(-5, 5)
107
  )
108
+
109
+ # Diminishing returns for small engines with big mods
110
+ if cyl < 6 and gain > 200:
111
+ gain *= 0.8
112
+
113
+ data.append([engine, cyl, base_hp, intake, exhaust, induction, cams, nitrous, fuel, tune, gain])
114
+
115
+ columns = ["engine", "cyl", "base_hp", "intake", "exhaust", "induction", "cams", "nitrous", "fuel", "tune", "hp_gain"]
116
+ df = pd.DataFrame(data, columns=columns)
117
+
118
+ X = df.drop("hp_gain", axis=1)
119
+ y = df["hp_gain"]
120
+
121
+ scaler = StandardScaler()
122
+ X_scaled = scaler.fit_transform(X)
123
+
124
+ model = KNeighborsRegressor(n_neighbors=7, weights='distance')
125
+ model.fit(X_scaled, y)
126
+
127
+ return scaler, model
128
+
129
+ scaler, model = train_model()
130
+
131
+ # ---------------------------------------------------------
132
+ # 3. SIDEBAR CONTROLS (The "Settings Pane")
133
+ # ---------------------------------------------------------
134
+
135
+ with st.sidebar:
136
+ st.header("βš™οΈ Configuration")
137
+
138
+ with st.expander("πŸš™ Base Vehicle Stats", expanded=True):
139
+ col_eng_1, col_eng_2 = st.columns(2)
140
+ with col_eng_1:
141
+ cyl = st.selectbox("Cylinders", [4, 5, 6, 8, 10, 12])
142
+ with col_eng_2:
143
+ engine = st.selectbox("Size (L)", [1.6, 2.0, 2.5, 3.0, 3.5, 4.0, 5.0, 5.2, 6.0, 6.5, 8.4])
144
+
145
+ base_hp = st.number_input("Factory HP", 100, 1000, 300)
146
+
147
+ with st.expander("πŸ”§ Bolt-on Modifications", expanded=True):
148
+ intake = st.selectbox("Intake", ["Stock", "High Flow Filter", "Cold Air Intake"])
149
+ exhaust = st.selectbox("Exhaust", ["Stock", "Cat-back", "Long Tube Headers", "Full Straight Pipe"])
150
+
151
+ with st.expander("πŸ”₯ Internals & Boost", expanded=True):
152
+ induction = st.selectbox("Forced Induction", ["Naturally Aspirated", "Single Turbo", "Twin Turbo", "Supercharger"])
153
+ cams = st.selectbox("Camshafts", ["Stock", "Street Profile", "Track/Race Profile"])
154
+ nitrous = st.checkbox("Nitrous Oxide System (NOS)", value=False)
155
+
156
+ with st.expander("πŸ’» Tuning & Fuel", expanded=True):
157
+ fuel = st.selectbox("Fuel Type", ["87 Octane", "91 Octane", "93 Octane", "E85 (Ethanol)"])
158
+ tune = st.selectbox("ECU Map", ["Stock Map", "Stage 1", "Stage 2"])
159
+
160
+ # ---------------------------------------------------------
161
+ # 4. MAIN DASHBOARD (The "App View")
162
+ # ---------------------------------------------------------
163
+
164
+ # Prediction Mappings
165
+ intake_map = {"Stock":0, "High Flow Filter":1, "Cold Air Intake":2}
166
+ exhaust_map = {"Stock":0, "Cat-back":1, "Long Tube Headers":2, "Full Straight Pipe":3}
167
+ induction_map = {"Naturally Aspirated":0, "Single Turbo":1, "Twin Turbo":2, "Supercharger":3}
168
+ cams_map = {"Stock":0, "Street Profile":1, "Track/Race Profile":2}
169
+ nitrous_map = {False:0, True:1}
170
+ fuel_map = {"87 Octane":0, "91 Octane":1, "93 Octane":2, "E85 (Ethanol)":3}
171
+ tune_map = {"Stock Map":0, "Stage 1":1, "Stage 2":2}
172
+
173
+ # Calculation
174
+ input_data = np.array([[
175
+ engine, cyl, base_hp,
176
+ intake_map[intake], exhaust_map[exhaust], induction_map[induction],
177
+ cams_map[cams], nitrous_map[nitrous], fuel_map[fuel], tune_map[tune]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
178
  ]])
179
 
180
+ pred = model.predict(scaler.transform(input_data))[0]
181
+ new_hp = base_hp + pred
182
+ pct_gain = (pred / base_hp) * 100
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
 
184
+ # --- UI LAYOUT ---
 
 
 
 
185
 
186
+ # 1. Header with Fake Window Controls
187
+ st.markdown("""
188
+ <div class="traffic-lights">
189
+ <div class="dot red"></div>
190
+ <div class="dot yellow"></div>
191
+ <div class="dot green"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
  </div>
193
+ """, unsafe_allow_html=True)
194
+
195
+ st.title("Performance Estimator Pro")
196
+ st.markdown(f"Analysis for **{cyl}-Cylinder {engine}L Engine**")
197
+
198
+ st.divider()
199
+
200
+ # 2. Main Hero Section (Glass Cards)
201
+ col1, col2 = st.columns([1.5, 1])
202
+
203
+ with col1:
204
+ st.markdown("### πŸ“ˆ Dyno Projection")
205
+ # Area chart looks cleaner/more modern than bar
206
+ chart_data = pd.DataFrame({
207
+ "Horsepower": [base_hp, new_hp],
208
+ "Stage": ["Factory", "Modified"]
209
+ })
210
+
211
+ # Custom color for the chart to match Apple blue
212
+ st.vega_lite_chart(chart_data, {
213
+ "mark": {"type": "bar", "cornerRadiusEnd": 4, "color": "#007AFF"},
214
+ "encoding": {
215
+ "x": {"field": "Stage", "type": "nominal", "axis": {"labelAngle": 0}},
216
+ "y": {"field": "Horsepower", "type": "quantitative"},
217
+ "tooltip": ["Stage", "Horsepower"]
218
+ }
219
+ }, use_container_width=True)
220
+
221
+ with col2:
222
+ st.markdown("### ⚑ Results")
223
+
224
+ # Container for the metrics
225
+ with st.container():
226
+ st.markdown(f"""
227
+ <div style="padding: 10px;">
228
+ <span style="font-size: 14px; color: #86868b; text-transform: uppercase; letter-spacing: 1px; font-weight: 600;">Total Power</span>
229
+ <div style="font-size: 48px; font-weight: 700; color: #1d1d1f; line-height: 1.2;">{int(new_hp)} HP</div>
230
+ </div>
231
+ """, unsafe_allow_html=True)
232
+
233
+ st.divider()
234
+
235
+ c1, c2 = st.columns(2)
236
+ with c1:
237
+ st.metric("Gain", f"+{int(pred)} HP")
238
+ with c2:
239
+ st.metric("Improvement", f"{pct_gain:.1f}%")
240
+
241
+ # 3. Spec Summary Row
242
+ st.markdown("### πŸ“‹ Build Summary")
243
+ with st.container():
244
+ c1, c2, c3, c4 = st.columns(4)
245
+ c1.info(f"**Induction:** {induction}")
246
+ c2.info(f"**Fuel:** {fuel}")
247
+ c3.info(f"**Camshafts:** {cams}")
248
+ c4.info(f"**Nitrous:** {'Enabled' if nitrous else 'Disabled'}")
249
+
250
+ if pct_gain > 50:
251
+ st.toast("πŸš€ Massive gains detected! Check transmission limits.", icon="⚠️")
252
 
253