142 lines
5.0 KiB
Python
142 lines
5.0 KiB
Python
|
|
import streamlit as st
|
||
|
|
import requests
|
||
|
|
import pandas as pd
|
||
|
|
import json
|
||
|
|
import os
|
||
|
|
import sys
|
||
|
|
|
||
|
|
# Add project root to path for imports
|
||
|
|
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')))
|
||
|
|
from src.etl.auto_pipeline import start_background_monitor
|
||
|
|
|
||
|
|
# Set page configuration
|
||
|
|
st.set_page_config(
|
||
|
|
page_title="Clutch-IQ: CS2 Strategy Simulator",
|
||
|
|
page_icon="💣",
|
||
|
|
layout="wide"
|
||
|
|
)
|
||
|
|
|
||
|
|
# Start Auto-Pipeline Service (Singleton)
|
||
|
|
@st.cache_resource
|
||
|
|
def start_pipeline_service():
|
||
|
|
"""Starts the auto-pipeline in the background once."""
|
||
|
|
start_background_monitor()
|
||
|
|
return True
|
||
|
|
|
||
|
|
start_pipeline_service()
|
||
|
|
|
||
|
|
# API Endpoint (Make sure your Flask app is running!)
|
||
|
|
API_URL = "http://127.0.0.1:5000/predict"
|
||
|
|
|
||
|
|
st.title("💣 Clutch-IQ: Win Rate Predictor")
|
||
|
|
st.markdown("Adjust the battlefield parameters to see how the win probability shifts.")
|
||
|
|
|
||
|
|
# --- Sidebar Controls ---
|
||
|
|
st.sidebar.header("Team Status")
|
||
|
|
|
||
|
|
# Alive Players
|
||
|
|
col1, col2 = st.sidebar.columns(2)
|
||
|
|
with col1:
|
||
|
|
t_alive = st.number_input("T Alive", min_value=1, max_value=5, value=2)
|
||
|
|
with col2:
|
||
|
|
ct_alive = st.number_input("CT Alive", min_value=1, max_value=5, value=2)
|
||
|
|
|
||
|
|
# Health
|
||
|
|
st.sidebar.subheader("Health Points")
|
||
|
|
t_health = st.sidebar.slider("T Total Health", min_value=1, max_value=t_alive*100, value=t_alive*80)
|
||
|
|
ct_health = st.sidebar.slider("CT Total Health", min_value=1, max_value=ct_alive*100, value=ct_alive*90)
|
||
|
|
|
||
|
|
# Economy
|
||
|
|
st.sidebar.subheader("Economy")
|
||
|
|
t_equip = st.sidebar.slider("T Equipment Value", min_value=0, max_value=30000, value=8000, step=100)
|
||
|
|
ct_equip = st.sidebar.slider("CT Equipment Value", min_value=0, max_value=30000, value=12000, step=100)
|
||
|
|
t_cash = st.sidebar.slider("T Cash Reserve", min_value=0, max_value=16000*5, value=5000, step=100)
|
||
|
|
ct_cash = st.sidebar.slider("CT Cash Reserve", min_value=0, max_value=16000*5, value=6000, step=100)
|
||
|
|
|
||
|
|
st.sidebar.subheader("Player Rating")
|
||
|
|
t_player_rating = st.sidebar.slider("T Avg Rating", min_value=0.0, max_value=2.5, value=1.0, step=0.01)
|
||
|
|
ct_player_rating = st.sidebar.slider("CT Avg Rating", min_value=0.0, max_value=2.5, value=1.0, step=0.01)
|
||
|
|
|
||
|
|
# Spatial & Context
|
||
|
|
st.sidebar.header("Tactical Situation")
|
||
|
|
team_distance = st.sidebar.slider("Team Distance (Avg)", 0, 4000, 1500, help="Average distance between T centroid and CT centroid")
|
||
|
|
t_spread = st.sidebar.slider("T Spread", 0, 2000, 500, help="How spread out the Terrorists are")
|
||
|
|
ct_spread = st.sidebar.slider("CT Spread", 0, 2000, 800, help="How spread out the Counter-Terrorists are")
|
||
|
|
t_pincer = st.sidebar.slider("T Pincer Index", 0.0, 1.0, 0.4, help="1.0 means perfect surround")
|
||
|
|
ct_pincer = st.sidebar.slider("CT Pincer Index", 0.0, 1.0, 0.2)
|
||
|
|
|
||
|
|
bomb_planted = st.sidebar.checkbox("Bomb Planted?", value=False)
|
||
|
|
site = st.sidebar.selectbox("Bombsite", ["A", "B"], index=0)
|
||
|
|
|
||
|
|
# --- Main Display ---
|
||
|
|
|
||
|
|
# Construct Payload
|
||
|
|
payload = {
|
||
|
|
"t_alive": t_alive,
|
||
|
|
"ct_alive": ct_alive,
|
||
|
|
"t_health": t_health,
|
||
|
|
"ct_health": ct_health,
|
||
|
|
"t_equip_value": t_equip,
|
||
|
|
"ct_equip_value": ct_equip,
|
||
|
|
"t_total_cash": t_cash,
|
||
|
|
"ct_total_cash": ct_cash,
|
||
|
|
"team_distance": team_distance,
|
||
|
|
"t_spread": t_spread,
|
||
|
|
"ct_spread": ct_spread,
|
||
|
|
"t_area": t_spread * 100, # Approximation for demo
|
||
|
|
"ct_area": ct_spread * 100, # Approximation for demo
|
||
|
|
"t_pincer_index": t_pincer,
|
||
|
|
"ct_pincer_index": ct_pincer,
|
||
|
|
"is_bomb_planted": int(bomb_planted),
|
||
|
|
"site": 0 if site == "A" else 1, # Simple encoding for demo
|
||
|
|
"game_time": 60.0,
|
||
|
|
"t_player_rating": t_player_rating,
|
||
|
|
"ct_player_rating": ct_player_rating
|
||
|
|
}
|
||
|
|
|
||
|
|
# Prediction
|
||
|
|
if st.button("Predict Win Rate", type="primary"):
|
||
|
|
try:
|
||
|
|
response = requests.post(API_URL, json=payload)
|
||
|
|
if response.status_code == 200:
|
||
|
|
result = response.json()
|
||
|
|
win_prob_obj = result.get("win_probability", {})
|
||
|
|
t_prob = float(win_prob_obj.get("T", 0.0))
|
||
|
|
ct_prob = float(win_prob_obj.get("CT", 0.0))
|
||
|
|
predicted = result.get("prediction", "Unknown")
|
||
|
|
|
||
|
|
col_a, col_b, col_c = st.columns(3)
|
||
|
|
with col_a:
|
||
|
|
st.metric(label="Prediction", value=predicted)
|
||
|
|
with col_b:
|
||
|
|
st.metric(label="T Win Probability", value=f"{t_prob:.2%}")
|
||
|
|
with col_c:
|
||
|
|
st.metric(label="CT Win Probability", value=f"{ct_prob:.2%}")
|
||
|
|
|
||
|
|
st.progress(t_prob)
|
||
|
|
|
||
|
|
if t_prob > ct_prob:
|
||
|
|
st.success("Terrorists have the advantage!")
|
||
|
|
else:
|
||
|
|
st.error("Counter-Terrorists have the advantage!")
|
||
|
|
|
||
|
|
with st.expander("Show Raw Input Data"):
|
||
|
|
st.json(payload)
|
||
|
|
|
||
|
|
with st.expander("Show Raw API Response"):
|
||
|
|
st.json(result)
|
||
|
|
|
||
|
|
else:
|
||
|
|
st.error(f"Error: {response.text}")
|
||
|
|
except requests.exceptions.ConnectionError:
|
||
|
|
st.error("Could not connect to Inference Service. Is `src/inference/app.py` running?")
|
||
|
|
|
||
|
|
# Tips
|
||
|
|
st.markdown("---")
|
||
|
|
st.markdown("""
|
||
|
|
### 💡 How to use:
|
||
|
|
1. Ensure the backend is running: `python src/inference/app.py`
|
||
|
|
2. Adjust sliders on the left.
|
||
|
|
3. Click **Predict Win Rate**.
|
||
|
|
""")
|