Update app/streamlit_app.py
Browse files- app/streamlit_app.py +55 -280
app/streamlit_app.py
CHANGED
|
@@ -17,17 +17,13 @@ from src.pipeline.predict_pipeline import PredictPipeline
|
|
| 17 |
from src.explanability.shap_explainer import ShapExplainer
|
| 18 |
from src.monitoring.db import save_to_db
|
| 19 |
|
| 20 |
-
|
| 21 |
-
st.set_page_config(page_title="Fraud Guard", layout="wide", initial_sidebar_state="expanded")
|
| 22 |
|
| 23 |
# --- Initialize Session State for Auto-Fill Buttons ---
|
| 24 |
-
if "
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
st.session_state[f"v_{i}"] = 0.0
|
| 29 |
-
st.session_state.initialized = True
|
| 30 |
-
|
| 31 |
|
| 32 |
def generate_sample(is_fraud=False):
|
| 33 |
"""Fills the UI with either a normal transaction or a simulated fraud attack"""
|
|
@@ -36,53 +32,51 @@ def generate_sample(is_fraud=False):
|
|
| 36 |
{
|
| 37 |
"Time": 406.0, "Amount": 0.00,
|
| 38 |
"V": [-2.312, 1.951, -1.609, 3.997, -0.522, -1.426, -2.537, 1.391, -2.770, -2.772,
|
| 39 |
-
|
| 40 |
-
|
| 41 |
},
|
| 42 |
{
|
| 43 |
"Time": 12500.0, "Amount": 99.99,
|
| 44 |
"V": [-0.95, 0.52, -1.53, 0.85, -0.21, 0.11, -0.45, 0.22, -0.63, -1.05,
|
| 45 |
-
|
| 46 |
-
|
| 47 |
},
|
| 48 |
{
|
| 49 |
"Time": 4462.0, "Amount": 1.00,
|
| 50 |
"V": [-2.303, 1.759, -0.359, 2.330, -0.821, -0.075, -0.560, 1.214, -1.385, -2.776,
|
| 51 |
-
|
| 52 |
-0.294, -0.932, 0.172, -0.087, -0.156, -0.542, 0.039, -0.153]
|
| 53 |
}
|
| 54 |
]
|
| 55 |
-
|
| 56 |
chosen_fraud = random.choice(fraud_database)
|
| 57 |
-
|
| 58 |
st.session_state.t_time = chosen_fraud["Time"]
|
| 59 |
st.session_state.t_amount = chosen_fraud["Amount"]
|
| 60 |
for i in range(1, 29):
|
| 61 |
-
st.session_state[f"v_{i}"] = chosen_fraud["V"][i
|
| 62 |
-
|
| 63 |
else:
|
| 64 |
st.session_state.t_time = random.uniform(100, 150000)
|
| 65 |
st.session_state.t_amount = random.uniform(5, 150)
|
| 66 |
for i in range(1, 29):
|
| 67 |
st.session_state[f"v_{i}"] = random.uniform(-1.0, 1.0)
|
| 68 |
|
| 69 |
-
|
| 70 |
# β
Cache ONLY heavy computation (safe)
|
| 71 |
@st.cache_data(show_spinner=False)
|
| 72 |
def get_prediction_and_shap(payload):
|
| 73 |
response = requests.post(API_URL, json=payload, timeout=30)
|
| 74 |
result = response.json()
|
| 75 |
-
|
| 76 |
input_df = pd.DataFrame([payload])
|
| 77 |
pipeline = PredictPipeline()
|
| 78 |
processed_df = pipeline.preprocess(input_df)
|
| 79 |
-
|
| 80 |
explainer = ShapExplainer()
|
| 81 |
shap_values = explainer.explain(processed_df)
|
| 82 |
-
|
| 83 |
return result, shap_values
|
| 84 |
|
| 85 |
-
|
| 86 |
# --- UI Sidebar & Navigation ---
|
| 87 |
page = st.sidebar.selectbox("π Choose Section", ["Prediction", "Drift Monitoring"])
|
| 88 |
|
|
@@ -98,7 +92,6 @@ if page == "Prediction":
|
|
| 98 |
|
| 99 |
with col1:
|
| 100 |
st.markdown("### π οΈ Demo Controls")
|
| 101 |
-
|
| 102 |
demo_col1, demo_col2 = st.columns(2)
|
| 103 |
with demo_col1:
|
| 104 |
if st.button("β
Simulate Normal User", use_container_width=True):
|
|
@@ -110,281 +103,62 @@ if page == "Prediction":
|
|
| 110 |
st.markdown("### π₯ Transaction Input")
|
| 111 |
with st.container(border=True):
|
| 112 |
with st.form("transaction_form"):
|
|
|
|
| 113 |
t_time = st.slider("Time (Sec)", 0.0, 172800.0, key="t_time")
|
| 114 |
t_amount = st.slider("Amount ($)", 0.0, 5000.0, key="t_amount")
|
| 115 |
-
|
|
|
|
|
|
|
| 116 |
with st.expander("PCA Feature Vectors (V1 - V28)", expanded=False):
|
| 117 |
-
v_data = {}
|
| 118 |
for i in range(1, 29):
|
| 119 |
v_data[f"V{i}"] = st.number_input(f"V{i}", key=f"v_{i}", format="%.4f")
|
| 120 |
-
|
| 121 |
st.markdown("---")
|
| 122 |
threshold = st.slider("AI Sensitivity (Threshold)", 0.05, 0.95, 0.15)
|
| 123 |
submit_btn = st.form_submit_button("π Run Analysis", use_container_width=True)
|
| 124 |
|
| 125 |
with col2:
|
| 126 |
st.markdown("### π Live Telemetry & Assessment")
|
| 127 |
-
|
| 128 |
-
result_container = st.container()
|
| 129 |
-
|
| 130 |
if not submit_btn:
|
| 131 |
st.info("Awaiting transaction payload. Click 'Simulate' then 'Run Analysis'.")
|
| 132 |
|
| 133 |
if submit_btn:
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
try:
|
| 138 |
-
with st.spinner("Analyzing threat vectors..."):
|
| 139 |
-
result, shap_values = get_prediction_and_shap(payload)
|
| 140 |
-
|
| 141 |
-
prob = result["fraud_probability"]
|
| 142 |
-
pred = 1 if prob > threshold else 0
|
| 143 |
-
action = "π« Block Transaction" if pred == 1 else "β
Allow Transaction"
|
| 144 |
-
|
| 145 |
-
if pred == 1:
|
| 146 |
-
st.error(f"π¨ FRAUD DETECTED: {action}")
|
| 147 |
-
else:
|
| 148 |
-
st.success(f"β
TRANSACTION SAFE: {action}")
|
| 149 |
-
|
| 150 |
-
m_col1, m_col2 = st.columns(2)
|
| 151 |
-
m_col1.metric("Risk Level", f"{prob:.4%}")
|
| 152 |
-
m_col2.metric("Prediction Output", pred)
|
| 153 |
-
|
| 154 |
-
st.progress(float(prob))
|
| 155 |
-
st.markdown("---")
|
| 156 |
-
|
| 157 |
-
st.subheader("π§ Explainable AI (SHAP)")
|
| 158 |
-
|
| 159 |
-
# β
NO CACHE HERE (fixes your error)
|
| 160 |
-
fig, ax = plt.subplots(figsize=(8, 4))
|
| 161 |
-
shap.plots.waterfall(shap_values[0], show=False)
|
| 162 |
-
st.pyplot(fig)
|
| 163 |
-
|
| 164 |
-
except Exception as e:
|
| 165 |
-
st.error(f"β³ Error connecting to API: {e}")
|
| 166 |
-
|
| 167 |
-
elif page == "Drift Monitoring":
|
| 168 |
-
st.title("π Data Drift Monitoring")
|
| 169 |
-
st.markdown("### Monitor model health & trigger verified retraining")
|
| 170 |
-
|
| 171 |
-
with st.expander("π οΈ Demo Tools: Force Data Drift", expanded=True):
|
| 172 |
-
st.write("Inject 50 heavily skewed transactions into the database to trigger a statistical drift warning.")
|
| 173 |
-
if st.button("π Inject Synthetic Drift Data", type="primary"):
|
| 174 |
-
with st.spinner("Injecting bad data into Cloud DB..."):
|
| 175 |
-
for _ in range(50):
|
| 176 |
-
skewed_data = {"Time": random.uniform(10, 50000), "Amount": random.uniform(1000, 5000)}
|
| 177 |
-
for i in range(1, 29):
|
| 178 |
-
skewed_data[f"V{i}"] = random.uniform(-15.0, 15.0)
|
| 179 |
-
save_to_db(skewed_data, pred=1, prob=0.99)
|
| 180 |
-
st.success("β
50 Skewed rows injected!")
|
| 181 |
-
|
| 182 |
-
st.markdown("---")
|
| 183 |
-
|
| 184 |
-
try:
|
| 185 |
-
from src.monitoring.drift import detect_drift
|
| 186 |
-
from src.pipeline.retrain_pipeline import retrain
|
| 187 |
-
except:
|
| 188 |
-
st.error("β οΈ Drift feature not supported in this environment")
|
| 189 |
-
st.stop()
|
| 190 |
-
|
| 191 |
-
if st.button("π Run Drift Detection"):
|
| 192 |
-
with st.spinner("Running statistical drift analysis..."):
|
| 193 |
try:
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 198 |
else:
|
| 199 |
-
st.
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
html = f.read()
|
| 207 |
-
components.html(html, height=800, scrolling=True)
|
| 208 |
-
|
| 209 |
-
st.markdown("---")
|
| 210 |
-
|
| 211 |
-
if st.session_state.get("drift_done", False):
|
| 212 |
-
if st.button("β‘ Retrain Model"):
|
| 213 |
-
with st.spinner("Retraining model..."):
|
| 214 |
-
try:
|
| 215 |
-
retrain()
|
| 216 |
-
st.success("β
Model retrained successfully!")
|
| 217 |
-
except Exception as e:
|
| 218 |
-
st.error(f"β Error: {e}")import streamlit as st
|
| 219 |
-
import pandas as pd
|
| 220 |
-
import sys
|
| 221 |
-
import os
|
| 222 |
-
import requests
|
| 223 |
-
import shap
|
| 224 |
-
import matplotlib.pyplot as plt
|
| 225 |
-
import streamlit.components.v1 as components
|
| 226 |
-
import random
|
| 227 |
-
from dotenv import load_dotenv
|
| 228 |
-
|
| 229 |
-
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
| 230 |
-
load_dotenv()
|
| 231 |
-
API_URL = os.getenv("API_URL", "http://127.0.0.1:8000/predict")
|
| 232 |
-
|
| 233 |
-
from src.pipeline.predict_pipeline import PredictPipeline
|
| 234 |
-
from src.explanability.shap_explainer import ShapExplainer
|
| 235 |
-
from src.monitoring.db import save_to_db
|
| 236 |
-
|
| 237 |
-
# β
Stable page config
|
| 238 |
-
st.set_page_config(page_title="Fraud Guard", layout="wide", initial_sidebar_state="expanded")
|
| 239 |
-
|
| 240 |
-
# --- Initialize Session State for Auto-Fill Buttons ---
|
| 241 |
-
if "initialized" not in st.session_state:
|
| 242 |
-
st.session_state.t_time = 10000.0
|
| 243 |
-
st.session_state.t_amount = 100.0
|
| 244 |
-
for i in range(1, 29):
|
| 245 |
-
st.session_state[f"v_{i}"] = 0.0
|
| 246 |
-
st.session_state.initialized = True
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
def generate_sample(is_fraud=False):
|
| 250 |
-
"""Fills the UI with either a normal transaction or a simulated fraud attack"""
|
| 251 |
-
if is_fraud:
|
| 252 |
-
fraud_database = [
|
| 253 |
-
{
|
| 254 |
-
"Time": 406.0, "Amount": 0.00,
|
| 255 |
-
"V": [-2.312, 1.951, -1.609, 3.997, -0.522, -1.426, -2.537, 1.391, -2.770, -2.772,
|
| 256 |
-
3.202, -2.899, -0.595, -4.289, 0.389, -1.140, -2.830, -0.016, 0.416, 0.126,
|
| 257 |
-
0.517, -0.035, -0.465, 0.320, 0.044, 0.177, 0.261, -0.143]
|
| 258 |
-
},
|
| 259 |
-
{
|
| 260 |
-
"Time": 12500.0, "Amount": 99.99,
|
| 261 |
-
"V": [-0.95, 0.52, -1.53, 0.85, -0.21, 0.11, -0.45, 0.22, -0.63, -1.05,
|
| 262 |
-
1.20, -1.55, 0.30, -2.01, 0.10, -0.55, -1.22, 0.20, 0.45, -0.10,
|
| 263 |
-
0.25, 0.15, -0.12, 0.05, 0.22, -0.15, 0.02, 0.05]
|
| 264 |
-
},
|
| 265 |
-
{
|
| 266 |
-
"Time": 4462.0, "Amount": 1.00,
|
| 267 |
-
"V": [-2.303, 1.759, -0.359, 2.330, -0.821, -0.075, -0.560, 1.214, -1.385, -2.776,
|
| 268 |
-
3.231, -2.719, -1.059, -3.535, -1.583, -1.488, -2.573, -0.739, 0.380, -0.430,
|
| 269 |
-
-0.294, -0.932, 0.172, -0.087, -0.156, -0.542, 0.039, -0.153]
|
| 270 |
-
}
|
| 271 |
-
]
|
| 272 |
-
|
| 273 |
-
chosen_fraud = random.choice(fraud_database)
|
| 274 |
-
|
| 275 |
-
st.session_state.t_time = chosen_fraud["Time"]
|
| 276 |
-
st.session_state.t_amount = chosen_fraud["Amount"]
|
| 277 |
-
for i in range(1, 29):
|
| 278 |
-
st.session_state[f"v_{i}"] = chosen_fraud["V"][i - 1]
|
| 279 |
-
|
| 280 |
-
else:
|
| 281 |
-
st.session_state.t_time = random.uniform(100, 150000)
|
| 282 |
-
st.session_state.t_amount = random.uniform(5, 150)
|
| 283 |
-
for i in range(1, 29):
|
| 284 |
-
st.session_state[f"v_{i}"] = random.uniform(-1.0, 1.0)
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
# β
Cache ONLY heavy computation (safe)
|
| 288 |
-
@st.cache_data(show_spinner=False)
|
| 289 |
-
def get_prediction_and_shap(payload):
|
| 290 |
-
response = requests.post(API_URL, json=payload, timeout=30)
|
| 291 |
-
result = response.json()
|
| 292 |
-
|
| 293 |
-
input_df = pd.DataFrame([payload])
|
| 294 |
-
pipeline = PredictPipeline()
|
| 295 |
-
processed_df = pipeline.preprocess(input_df)
|
| 296 |
-
|
| 297 |
-
explainer = ShapExplainer()
|
| 298 |
-
shap_values = explainer.explain(processed_df)
|
| 299 |
-
|
| 300 |
-
return result, shap_values
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
# --- UI Sidebar & Navigation ---
|
| 304 |
-
page = st.sidebar.selectbox("π Choose Section", ["Prediction", "Drift Monitoring"])
|
| 305 |
-
|
| 306 |
-
if page == "Prediction":
|
| 307 |
-
st.markdown("""
|
| 308 |
-
<div style='text-align: center; padding: 1rem 0;'>
|
| 309 |
-
<h1 style='color: #1E3A8A;'>π³ Fraud Guard Intelligence</h1>
|
| 310 |
-
<p style='color: #6B7280; font-size: 1.2rem;'>Real-Time Transaction Risk Analysis</p>
|
| 311 |
-
</div>
|
| 312 |
-
""", unsafe_allow_html=True)
|
| 313 |
-
|
| 314 |
-
col1, col2 = st.columns([1, 2])
|
| 315 |
-
|
| 316 |
-
with col1:
|
| 317 |
-
st.markdown("### π οΈ Demo Controls")
|
| 318 |
-
|
| 319 |
-
demo_col1, demo_col2 = st.columns(2)
|
| 320 |
-
with demo_col1:
|
| 321 |
-
if st.button("β
Simulate Normal User", use_container_width=True):
|
| 322 |
-
generate_sample(is_fraud=False)
|
| 323 |
-
with demo_col2:
|
| 324 |
-
if st.button("π¨ Simulate Fraud Attack", type="primary", use_container_width=True):
|
| 325 |
-
generate_sample(is_fraud=True)
|
| 326 |
-
|
| 327 |
-
st.markdown("### π₯ Transaction Input")
|
| 328 |
-
with st.container(border=True):
|
| 329 |
-
with st.form("transaction_form"):
|
| 330 |
-
t_time = st.slider("Time (Sec)", 0.0, 172800.0, key="t_time")
|
| 331 |
-
t_amount = st.slider("Amount ($)", 0.0, 5000.0, key="t_amount")
|
| 332 |
-
|
| 333 |
-
with st.expander("PCA Feature Vectors (V1 - V28)", expanded=False):
|
| 334 |
-
v_data = {}
|
| 335 |
-
for i in range(1, 29):
|
| 336 |
-
v_data[f"V{i}"] = st.number_input(f"V{i}", key=f"v_{i}", format="%.4f")
|
| 337 |
-
|
| 338 |
st.markdown("---")
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
if not submit_btn:
|
| 348 |
-
st.info("Awaiting transaction payload. Click 'Simulate' then 'Run Analysis'.")
|
| 349 |
-
|
| 350 |
-
if submit_btn:
|
| 351 |
-
with result_container:
|
| 352 |
-
payload = {"Time": st.session_state.t_time, "Amount": st.session_state.t_amount, **v_data}
|
| 353 |
-
|
| 354 |
-
try:
|
| 355 |
-
with st.spinner("Analyzing threat vectors..."):
|
| 356 |
-
result, shap_values = get_prediction_and_shap(payload)
|
| 357 |
-
|
| 358 |
-
prob = result["fraud_probability"]
|
| 359 |
-
pred = 1 if prob > threshold else 0
|
| 360 |
-
action = "π« Block Transaction" if pred == 1 else "β
Allow Transaction"
|
| 361 |
-
|
| 362 |
-
if pred == 1:
|
| 363 |
-
st.error(f"π¨ FRAUD DETECTED: {action}")
|
| 364 |
-
else:
|
| 365 |
-
st.success(f"β
TRANSACTION SAFE: {action}")
|
| 366 |
-
|
| 367 |
-
m_col1, m_col2 = st.columns(2)
|
| 368 |
-
m_col1.metric("Risk Level", f"{prob:.4%}")
|
| 369 |
-
m_col2.metric("Prediction Output", pred)
|
| 370 |
|
| 371 |
-
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
st.subheader("π§ Explainable AI (SHAP)")
|
| 375 |
-
|
| 376 |
-
# β
NO CACHE HERE (fixes your error)
|
| 377 |
-
fig, ax = plt.subplots(figsize=(8, 4))
|
| 378 |
-
shap.plots.waterfall(shap_values[0], show=False)
|
| 379 |
-
st.pyplot(fig)
|
| 380 |
-
|
| 381 |
-
except Exception as e:
|
| 382 |
-
st.error(f"β³ Error connecting to API: {e}")
|
| 383 |
|
| 384 |
elif page == "Drift Monitoring":
|
| 385 |
st.title("π Data Drift Monitoring")
|
| 386 |
st.markdown("### Monitor model health & trigger verified retraining")
|
| 387 |
-
|
| 388 |
with st.expander("π οΈ Demo Tools: Force Data Drift", expanded=True):
|
| 389 |
st.write("Inject 50 heavily skewed transactions into the database to trigger a statistical drift warning.")
|
| 390 |
if st.button("π Inject Synthetic Drift Data", type="primary"):
|
|
@@ -394,7 +168,7 @@ elif page == "Drift Monitoring":
|
|
| 394 |
for i in range(1, 29):
|
| 395 |
skewed_data[f"V{i}"] = random.uniform(-15.0, 15.0)
|
| 396 |
save_to_db(skewed_data, pred=1, prob=0.99)
|
| 397 |
-
st.success("β
50 Skewed rows injected!")
|
| 398 |
|
| 399 |
st.markdown("---")
|
| 400 |
|
|
@@ -413,7 +187,7 @@ elif page == "Drift Monitoring":
|
|
| 413 |
st.success("β
Drift report generated!")
|
| 414 |
st.session_state["drift_done"] = True
|
| 415 |
else:
|
| 416 |
-
st.warning("β οΈ Not enough data in live DB.")
|
| 417 |
except Exception as e:
|
| 418 |
st.error(f"β οΈ Error running drift: {e}")
|
| 419 |
|
|
@@ -424,12 +198,13 @@ elif page == "Drift Monitoring":
|
|
| 424 |
components.html(html, height=800, scrolling=True)
|
| 425 |
|
| 426 |
st.markdown("---")
|
|
|
|
| 427 |
|
| 428 |
if st.session_state.get("drift_done", False):
|
| 429 |
-
if st.button("β‘ Retrain Model"):
|
| 430 |
with st.spinner("Retraining model..."):
|
| 431 |
try:
|
| 432 |
retrain()
|
| 433 |
-
st.success("β
Model retrained successfully!")
|
| 434 |
except Exception as e:
|
| 435 |
st.error(f"β Error: {e}")
|
|
|
|
| 17 |
from src.explanability.shap_explainer import ShapExplainer
|
| 18 |
from src.monitoring.db import save_to_db
|
| 19 |
|
| 20 |
+
st.set_page_config(page_title="Fraud Guard", layout="wide")
|
|
|
|
| 21 |
|
| 22 |
# --- Initialize Session State for Auto-Fill Buttons ---
|
| 23 |
+
if "t_time" not in st.session_state: st.session_state.t_time = 10000.0
|
| 24 |
+
if "t_amount" not in st.session_state: st.session_state.t_amount = 100.0
|
| 25 |
+
for i in range(1, 29):
|
| 26 |
+
if f"v_{i}" not in st.session_state: st.session_state[f"v_{i}"] = 0.0
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
def generate_sample(is_fraud=False):
|
| 29 |
"""Fills the UI with either a normal transaction or a simulated fraud attack"""
|
|
|
|
| 32 |
{
|
| 33 |
"Time": 406.0, "Amount": 0.00,
|
| 34 |
"V": [-2.312, 1.951, -1.609, 3.997, -0.522, -1.426, -2.537, 1.391, -2.770, -2.772,
|
| 35 |
+
3.202, -2.899, -0.595, -4.289, 0.389, -1.140, -2.830, -0.016, 0.416, 0.126,
|
| 36 |
+
0.517, -0.035, -0.465, 0.320, 0.044, 0.177, 0.261, -0.143]
|
| 37 |
},
|
| 38 |
{
|
| 39 |
"Time": 12500.0, "Amount": 99.99,
|
| 40 |
"V": [-0.95, 0.52, -1.53, 0.85, -0.21, 0.11, -0.45, 0.22, -0.63, -1.05,
|
| 41 |
+
1.20, -1.55, 0.30, -2.01, 0.10, -0.55, -1.22, 0.20, 0.45, -0.10,
|
| 42 |
+
0.25, 0.15, -0.12, 0.05, 0.22, -0.15, 0.02, 0.05]
|
| 43 |
},
|
| 44 |
{
|
| 45 |
"Time": 4462.0, "Amount": 1.00,
|
| 46 |
"V": [-2.303, 1.759, -0.359, 2.330, -0.821, -0.075, -0.560, 1.214, -1.385, -2.776,
|
| 47 |
+
3.231, -2.719, -1.059, -3.535, -1.583, -1.488, -2.573, -0.739, 0.380, -0.430,
|
| 48 |
-0.294, -0.932, 0.172, -0.087, -0.156, -0.542, 0.039, -0.153]
|
| 49 |
}
|
| 50 |
]
|
| 51 |
+
|
| 52 |
chosen_fraud = random.choice(fraud_database)
|
| 53 |
+
|
| 54 |
st.session_state.t_time = chosen_fraud["Time"]
|
| 55 |
st.session_state.t_amount = chosen_fraud["Amount"]
|
| 56 |
for i in range(1, 29):
|
| 57 |
+
st.session_state[f"v_{i}"] = chosen_fraud["V"][i-1]
|
| 58 |
+
|
| 59 |
else:
|
| 60 |
st.session_state.t_time = random.uniform(100, 150000)
|
| 61 |
st.session_state.t_amount = random.uniform(5, 150)
|
| 62 |
for i in range(1, 29):
|
| 63 |
st.session_state[f"v_{i}"] = random.uniform(-1.0, 1.0)
|
| 64 |
|
|
|
|
| 65 |
# β
Cache ONLY heavy computation (safe)
|
| 66 |
@st.cache_data(show_spinner=False)
|
| 67 |
def get_prediction_and_shap(payload):
|
| 68 |
response = requests.post(API_URL, json=payload, timeout=30)
|
| 69 |
result = response.json()
|
| 70 |
+
|
| 71 |
input_df = pd.DataFrame([payload])
|
| 72 |
pipeline = PredictPipeline()
|
| 73 |
processed_df = pipeline.preprocess(input_df)
|
| 74 |
+
|
| 75 |
explainer = ShapExplainer()
|
| 76 |
shap_values = explainer.explain(processed_df)
|
| 77 |
+
|
| 78 |
return result, shap_values
|
| 79 |
|
|
|
|
| 80 |
# --- UI Sidebar & Navigation ---
|
| 81 |
page = st.sidebar.selectbox("π Choose Section", ["Prediction", "Drift Monitoring"])
|
| 82 |
|
|
|
|
| 92 |
|
| 93 |
with col1:
|
| 94 |
st.markdown("### π οΈ Demo Controls")
|
|
|
|
| 95 |
demo_col1, demo_col2 = st.columns(2)
|
| 96 |
with demo_col1:
|
| 97 |
if st.button("β
Simulate Normal User", use_container_width=True):
|
|
|
|
| 103 |
st.markdown("### π₯ Transaction Input")
|
| 104 |
with st.container(border=True):
|
| 105 |
with st.form("transaction_form"):
|
| 106 |
+
|
| 107 |
t_time = st.slider("Time (Sec)", 0.0, 172800.0, key="t_time")
|
| 108 |
t_amount = st.slider("Amount ($)", 0.0, 5000.0, key="t_amount")
|
| 109 |
+
|
| 110 |
+
# β
Ensure v_data always exists
|
| 111 |
+
v_data = {}
|
| 112 |
with st.expander("PCA Feature Vectors (V1 - V28)", expanded=False):
|
|
|
|
| 113 |
for i in range(1, 29):
|
| 114 |
v_data[f"V{i}"] = st.number_input(f"V{i}", key=f"v_{i}", format="%.4f")
|
| 115 |
+
|
| 116 |
st.markdown("---")
|
| 117 |
threshold = st.slider("AI Sensitivity (Threshold)", 0.05, 0.95, 0.15)
|
| 118 |
submit_btn = st.form_submit_button("π Run Analysis", use_container_width=True)
|
| 119 |
|
| 120 |
with col2:
|
| 121 |
st.markdown("### π Live Telemetry & Assessment")
|
|
|
|
|
|
|
|
|
|
| 122 |
if not submit_btn:
|
| 123 |
st.info("Awaiting transaction payload. Click 'Simulate' then 'Run Analysis'.")
|
| 124 |
|
| 125 |
if submit_btn:
|
| 126 |
+
payload = {"Time": st.session_state.t_time, "Amount": st.session_state.t_amount, **v_data}
|
| 127 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
try:
|
| 129 |
+
with st.spinner("Analyzing threat vectors..."):
|
| 130 |
+
result, shap_values = get_prediction_and_shap(payload)
|
| 131 |
+
|
| 132 |
+
prob = result["fraud_probability"]
|
| 133 |
+
pred = 1 if prob > threshold else 0
|
| 134 |
+
action = "π« Block Transaction" if pred == 1 else "β
Allow Transaction"
|
| 135 |
+
|
| 136 |
+
if pred == 1:
|
| 137 |
+
st.error(f"π¨ FRAUD DETECTED: {action}")
|
| 138 |
else:
|
| 139 |
+
st.success(f"β
TRANSACTION SAFE: {action}")
|
| 140 |
+
|
| 141 |
+
m_col1, m_col2 = st.columns(2)
|
| 142 |
+
m_col1.metric("Risk Level", f"{prob:.4%}")
|
| 143 |
+
m_col2.metric("Prediction Output", pred)
|
| 144 |
+
|
| 145 |
+
st.progress(float(prob))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 146 |
st.markdown("---")
|
| 147 |
+
|
| 148 |
+
st.subheader("π§ Explainable AI (SHAP)")
|
| 149 |
+
|
| 150 |
+
# β
FIX: No caching here (prevents shap hashing error)
|
| 151 |
+
fig, ax = plt.subplots(figsize=(8, 4))
|
| 152 |
+
shap.plots.waterfall(shap_values[0], show=False)
|
| 153 |
+
st.pyplot(fig)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 154 |
|
| 155 |
+
except Exception as e:
|
| 156 |
+
st.error(f"β³ Error connecting to API: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 157 |
|
| 158 |
elif page == "Drift Monitoring":
|
| 159 |
st.title("π Data Drift Monitoring")
|
| 160 |
st.markdown("### Monitor model health & trigger verified retraining")
|
| 161 |
+
|
| 162 |
with st.expander("π οΈ Demo Tools: Force Data Drift", expanded=True):
|
| 163 |
st.write("Inject 50 heavily skewed transactions into the database to trigger a statistical drift warning.")
|
| 164 |
if st.button("π Inject Synthetic Drift Data", type="primary"):
|
|
|
|
| 168 |
for i in range(1, 29):
|
| 169 |
skewed_data[f"V{i}"] = random.uniform(-15.0, 15.0)
|
| 170 |
save_to_db(skewed_data, pred=1, prob=0.99)
|
| 171 |
+
st.success("β
50 Skewed rows injected! Now click 'Run Drift Detection' below.")
|
| 172 |
|
| 173 |
st.markdown("---")
|
| 174 |
|
|
|
|
| 187 |
st.success("β
Drift report generated!")
|
| 188 |
st.session_state["drift_done"] = True
|
| 189 |
else:
|
| 190 |
+
st.warning("β οΈ Not enough data in live DB (Needs 50 rows). Use the Demo Injector above!")
|
| 191 |
except Exception as e:
|
| 192 |
st.error(f"β οΈ Error running drift: {e}")
|
| 193 |
|
|
|
|
| 198 |
components.html(html, height=800, scrolling=True)
|
| 199 |
|
| 200 |
st.markdown("---")
|
| 201 |
+
st.subheader("π Human-in-the-Loop Retraining")
|
| 202 |
|
| 203 |
if st.session_state.get("drift_done", False):
|
| 204 |
+
if st.button("β‘ Retrain Model (Requires Verified Data)"):
|
| 205 |
with st.spinner("Retraining model..."):
|
| 206 |
try:
|
| 207 |
retrain()
|
| 208 |
+
st.success("β
Model retrained successfully with verified data!")
|
| 209 |
except Exception as e:
|
| 210 |
st.error(f"β Error: {e}")
|