NeuralGearheads's picture
Update src/streamlit_app.py
945ffe8 verified
Raw
History Blame
15.8 kB
import streamlit as st
import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsRegressor
# ---------------------------
# Page config
# ---------------------------
st.set_page_config(
page_title="Neural Tuner – Car Mod Performance Estimator",
page_icon="πŸ”₯",
layout="wide"
)
# ---------------------------
# Global CSS – crazy but clean
# ---------------------------
st.markdown(
"""
<style>
html, body, [data-testid="stAppViewContainer"] {
background: radial-gradient(circle at top, #020617 0, #020617 35%, #020617 40%, #000000 100%) !important;
color: #e5e7eb;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "SF Pro Text",
"Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}
/* Hide default Streamlit header/menu */
[data-testid="stHeader"] { background: transparent; }
[data-testid="stToolbar"] { display: none; }
.hero {
border-radius: 24px;
padding: 18px 22px;
background: radial-gradient(circle at top left, rgba(96,165,250,0.35), transparent 55%),
radial-gradient(circle at bottom right, rgba(236,72,153,0.35), transparent 55%),
rgba(15,23,42,0.94);
box-shadow: 0 25px 60px rgba(15,23,42,0.9);
border: 1px solid rgba(148,163,184,0.35);
position: relative;
overflow: hidden;
}
.hero-title {
font-size: 1.9rem;
font-weight: 700;
background: linear-gradient(90deg, #f97316, #facc15, #22c55e, #38bdf8, #a855f7, #f97316);
background-size: 400% 100%;
-webkit-background-clip: text;
color: transparent;
animation: moveGradient 9s ease infinite;
}
.hero-sub {
color: #9ca3af;
font-size: 0.95rem;
}
.hero-pill {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 3px 11px;
border-radius: 999px;
background: rgba(15,118,110,0.2);
border: 1px solid rgba(34,197,94,0.6);
font-size: 0.75rem;
color: #bbf7d0;
margin-right: 8px;
}
@keyframes moveGradient {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
.glass {
background: radial-gradient(circle at top left, rgba(148,163,184,0.24), transparent 55%),
rgba(15,23,42,0.96);
border-radius: 18px;
padding: 18px 18px 14px 18px;
border: 1px solid rgba(148,163,184,0.4);
box-shadow: 0 20px 40px rgba(15,23,42,0.6);
}
.chip {
display:inline-block;
padding:4px 10px;
margin:3px 4px 3px 0;
border-radius:999px;
font-size:0.78rem;
background:rgba(59,130,246,0.14);
border:1px solid rgba(59,130,246,0.45);
color:#bfdbfe;
}
.meter-label {
font-size: 0.85rem;
color: #9ca3af;
margin-bottom: 3px;
}
.muted {
font-size: 0.8rem;
color: #6b7280;
}
/* Tabs */
button[data-baseweb="tab"] {
background: transparent !important;
border-radius: 999px !important;
padding: 0.5rem 1rem !important;
margin-right: 0.4rem;
color: #9ca3af !important;
}
button[data-baseweb="tab"][aria-selected="true"] {
background: rgba(59,130,246,0.2) !important;
color: #e5e7eb !important;
box-shadow: 0 0 0 1px rgba(59,130,246,0.7);
}
</style>
""",
unsafe_allow_html=True
)
# ---------------------------
# Data & model (same logic)
# ---------------------------
def generate_dataset(n=400):
np.random.seed(42)
data = []
for _ in range(n):
engine = np.random.choice([1.6, 2.0, 2.5, 3.0, 3.5, 5.0])
cyl = np.random.choice([4, 6, 8])
base_hp = int(engine * cyl * np.random.uniform(18, 22))
intake = np.random.choice([0, 1, 2])
exhaust = np.random.choice([0, 1, 2])
induction = np.random.choice([0, 1, 2])
fuel = np.random.choice([0, 1, 2, 3])
tune = np.random.choice([0, 1, 2])
altitude = np.random.uniform(0, 2000)
hp_gain = (
intake * np.random.uniform(3, 10) +
exhaust * np.random.uniform(5, 20) +
induction * np.random.uniform(25, 100) +
tune * np.random.uniform(10, 35) +
fuel * np.random.uniform(2, 8) -
altitude * 0.01 +
np.random.uniform(-3, 3)
)
data.append([engine, cyl, base_hp, intake, exhaust,
induction, fuel, tune, altitude, hp_gain])
columns = [
"engine", "cyl", "base_hp", "intake", "exhaust",
"induction", "fuel", "tune", "altitude", "hp_gain"
]
return pd.DataFrame(data, columns=columns)
df = generate_dataset()
X = df.drop("hp_gain", axis=1)
y = df["hp_gain"]
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
model = KNeighborsRegressor(n_neighbors=5, weights="distance")
model.fit(X_scaled, y)
# ---------------------------
# HERO SECTION
# ---------------------------
st.markdown(
"""
<div class="hero">
<div style="display:flex;justify-content:space-between;align-items:center;gap:14px;">
<div>
<div class="hero-pill">βš™οΈ Powered by KNN + synthetic dyno data</div>
<div class="hero-title">Neural Tuner – Live Car Mod Performance Lab</div>
<p class="hero-sub">
Mix intakes, turbos, tunes & fuel in real time. Watch the HP jump,
the power-to-weight spike and your virtual build go absolutely feral. 🐺
</p>
</div>
<div style="
width:170px;height:110px;
border-radius:22px;
background:conic-gradient(from 220deg,
#22c55e, #38bdf8, #a855f7, #f97316, #facc15, #22c55e);
padding:2px;
">
<div style="
width:100%;height:100%;
border-radius:19px;
background:radial-gradient(circle at 30% 0%, rgba(248,250,252,0.2), transparent 55%),
#020617;">
<div style="display:flex;flex-direction:column;justify-content:center;align-items:center;height:100%;">
<div style="font-size:0.78rem;color:#9ca3af;">Live Build</div>
<div style="font-size:1.6rem;font-weight:700;color:#e5e7eb;">HP Lab</div>
<div style="font-size:0.75rem;color:#22c55e;">Realtime estimator</div>
</div>
</div>
</div>
</div>
</div>
""",
unsafe_allow_html=True
)
st.markdown("")
# ---------------------------
# Layout: left (controls) / right (dashboard)
# ---------------------------
left, right = st.columns([1.15, 1])
# ===== LEFT: TUNING CONTROLS =====
with left:
st.markdown('<div class="glass">', unsafe_allow_html=True)
st.subheader("πŸŽ›οΈ Tune Your Build")
tabs = st.tabs(["Core Specs", "Bolt-Ons", "Boost & Cooling", "ECU & Fuel"])
# --- Core specs tab ---
with tabs[0]:
col1, col2 = st.columns(2)
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,7.0,8.0]
engine_disp = col1.selectbox("Engine Displacement (L)", engine_disp_options, index=engine_disp_options.index(2.0))
engine_layout = col2.selectbox("Engine Layout", ["I3","I4","I6","V6","V8","V10","V12"], index=2)
layout_to_cyl = {"I3":3,"I4":4,"I6":6,"V6":6,"V8":8,"V10":10,"V12":12}
cyl = layout_to_cyl[engine_layout]
base_hp = st.number_input(
"Base Horsepower (stock dyno)",
min_value=60, max_value=1400,
value=int(max(90, round(engine_disp * cyl * 20)))
)
col3, col4 = st.columns(2)
weight_kg = col3.number_input("Vehicle Weight (kg)", min_value=700, max_value=4000, value=1500)
weight_reduction = col4.slider("Weight Reduction (%)", 0, 40, 0)
# --- Bolt-ons tab ---
with tabs[1]:
col1, col2, col3 = st.columns(3)
intake = col1.selectbox("Intake", ["Stock", "Cold Air", "Performance"])
headers = col2.selectbox("Headers", ["Stock", "Shorty", "Long Tube"])
exhaust = col3.selectbox("Exhaust", ["Stock", "Cat-back", "Straight Pipe"])
exhaust_dia = st.slider("Exhaust Diameter (mm)", 40, 120, 60)
cam = st.selectbox("Cam Profile", ["Stock", "Stage 1 – Road", "Stage 2 – Aggressive", "Stage 3 – Race"])
intake_manifold = st.selectbox("Intake Manifold", ["Stock", "High-flow", "Individual throttle bodies"])
# --- Boost & cooling tab ---
with tabs[2]:
induction = st.selectbox("Forced Induction Setup", ["None", "Turbo", "Twin-Turbo", "Supercharger", "Twincharged"])
boost_psi = st.slider("Target Boost (psi)", 0, 40, 10)
turbo_size = st.selectbox("Turbo Size", ["N/A", "Small 45-55mm", "Medium 56-65mm", "Big 66-75mm", "XL 76mm+"])
intercooler = st.selectbox("Intercooler Type", ["None", "Air-to-Air", "Air-to-Water", "Front-mount High-Flow"])
meth = st.checkbox("Methanol Injection Kit", value=False)
# --- ECU & fuel tab ---
with tabs[3]:
tune = st.selectbox("ECU Tune Level", ["None", "Mild Street", "Stage 1", "Stage 2", "Kill Mode"])
fuel = st.selectbox("Fuel Type", ["87", "91", "93", "E85 / Race Blend"])
altitude = st.slider("Altitude (meters)", 0, 3500, 200)
traction_mode = st.radio("Traction Mode Vibe", ["Daily", "Spirited", "Track / Drag"], horizontal=True)
st.markdown(
'<p class="muted" style="margin-top:8px;">Numbers are synthetic; this is a dyno-inspired playground, not a tuning bible. πŸ§ͺ</p>',
unsafe_allow_html=True
)
st.markdown('</div>', unsafe_allow_html=True)
# ===== MODEL INPUT & CALC =====
# maps for model
intake_map = {"Stock":0, "Cold Air":1, "Performance":2}
exhaust_map = {"Stock":0, "Cat-back":1, "Straight Pipe":2}
induction_model_map = {"None":0, "Turbo":1, "Twin-Turbo":1, "Supercharger":2, "Twincharged":2}
fuel_map = {"87":0, "91":1, "93":2, "E85 / Race Blend":3}
tune_base_map = {"None":0, "Mild Street":1, "Stage 1":1, "Stage 2":2, "Kill Mode":2}
input_for_model = np.array([[
engine_disp,
cyl,
base_hp,
intake_map.get(intake,0),
exhaust_map.get(exhaust,0),
induction_model_map.get(induction,0),
fuel_map.get(fuel,0),
tune_base_map.get(tune,0),
altitude
]])
input_scaled = scaler.transform(input_for_model)
pred_base = float(model.predict(input_scaled)[0])
# --- heuristic extras ---
cam_gain_map = {
"Stock":0.0,
"Stage 1 – Road":6.0,
"Stage 2 – Aggressive":12.0,
"Stage 3 – Race":20.0
}
headers_gain_map = {"Stock":0.0, "Shorty":5.0, "Long Tube":9.0}
intake_man_gain_map = {
"Stock":0.0,
"High-flow":5.0,
"Individual throttle bodies":10.0
}
intercooler_gain_map = {
"None":0.0,
"Air-to-Air":4.0,
"Air-to-Water":6.5,
"Front-mount High-Flow":9.0
}
turbo_size_map = {
"N/A":0.0,
"Small 45-55mm":5.0,
"Medium 56-65mm":12.0,
"Big 66-75mm":20.0,
"XL 76mm+":28.0
}
cam_gain = cam_gain_map.get(cam, 0.0)
headers_gain = headers_gain_map.get(headers, 0.0)
intake_man_gain = intake_man_gain_map.get(intake_manifold, 0.0)
intercooler_gain = intercooler_gain_map.get(intercooler, 0.0)
turbo_size_gain = turbo_size_map.get(turbo_size, 0.0)
if induction in ["Turbo", "Twin-Turbo"]:
boost_gain = boost_psi * 1.8
elif induction in ["Supercharger", "Twincharged"]:
boost_gain = boost_psi * 1.4
else:
boost_gain = 0.0
meth_gain = 12.0 if meth else 0.0
exhaust_dia_gain = max(0.0, (exhaust_dia - 55) * 0.1)
extra_gain = (
cam_gain +
headers_gain +
intake_man_gain +
intercooler_gain +
turbo_size_gain +
boost_gain * 0.9 +
meth_gain +
exhaust_dia_gain
)
# traction / vibe adjusts "usable feel"
traction_multiplier = {"Daily":0.9, "Spirited":1.0, "Track / Drag":1.05}[traction_mode]
pred_total_gain = (pred_base + extra_gain) * traction_multiplier
pred_total_gain = max(pred_total_gain, -5.0) # clamp
new_hp = max(base_hp + pred_total_gain, 40.0)
effective_weight = weight_kg * (1 - weight_reduction / 100.0)
hp_per_ton = new_hp / (effective_weight / 1000.0)
# normalized hype level 0–100
hype_raw = np.clip(pred_total_gain / 120 * 100, 0, 100)
hype_level = int(hype_raw)
# verdict text
if hype_level < 20:
verdict = "Sleeper grocery getter πŸš™"
elif hype_level < 40:
verdict = "Respectable street build πŸŒƒ"
elif hype_level < 70:
verdict = "Serious weekend weapon βš”οΈ"
else:
verdict = "Full send, tyres cry for mercy 🏁"
# ===== RIGHT: DASHBOARD =====
with right:
st.markdown('<div class="glass">', unsafe_allow_html=True)
st.subheader("πŸ’₯ Build Outcome")
m1, m2, m3 = st.columns(3)
m1.metric("Estimated HP Gain", f"{pred_total_gain:.1f} HP")
m2.metric("New Output", f"{new_hp:.1f} HP")
m3.metric("HP per Ton", f"{hp_per_ton:.1f}")
st.markdown("")
st.markdown('<div class="meter-label">Hype Meter (relative craziness of this build)</div>', unsafe_allow_html=True)
st.progress(hype_level)
st.markdown(f"**Verdict:** {verdict}")
# small bar chart: stock vs tuned
st.markdown("")
st.bar_chart(
pd.DataFrame(
{"Horsepower": [base_hp, new_hp]},
index=["Stock", "Tuned"]
)
)
# chips summary
st.markdown(
f"""
<div style="margin-top:6px;">
<span class="chip">{engine_disp}L {engine_layout}</span>
<span class="chip">Weight: {weight_kg} kg β–Έ βˆ’{weight_reduction}%</span>
<span class="chip">Intake: {intake}</span>
<span class="chip">Headers: {headers}</span>
<span class="chip">Exhaust: {exhaust} ({exhaust_dia}mm)</span><br/>
<span class="chip">Induction: {induction} @ {boost_psi} psi</span>
<span class="chip">IC: {intercooler}</span>
<span class="chip">Tune: {tune}</span>
<span class="chip">Fuel: {fuel}</span>
</div>
""",
unsafe_allow_html=True
)
st.markdown("---")
# Contribution breakdown (no Styler.hide_index)
breakdown = pd.DataFrame({
"Component": [
"Model base (from dataset)",
"Cam profile",
"Headers",
"Intake manifold",
"Intercooler",
"Turbo size",
"Boost (net)",
"Meth kit",
"Exhaust diameter tweak"
],
"Approx HP": [
pred_base,
cam_gain,
headers_gain,
intake_man_gain,
intercooler_gain,
turbo_size_gain,
boost_gain * 0.9,
meth_gain,
exhaust_dia_gain
]
})
st.caption("πŸ”¬ Where is that extra power coming from?")
st.dataframe(breakdown, use_container_width=True, height=260)
st.markdown(
'<p class="muted" style="margin-top:8px;">Model: distance-weighted KNN on synthetic dyno-style data + extra heuristic math for advanced mods.</p>',
unsafe_allow_html=True
)
st.markdown('</div>', unsafe_allow_html=True)
# ===== FOOTER =====
st.markdown(
"""
<div style="text-align:center;margin-top:10px;" class="muted">
Built for fun, learning & ridiculous builds – drop this in a Space and watch car nerds lose it. πŸ”§πŸ”₯
</div>
""",
unsafe_allow_html=True
)