Update app/streamlit_app.py
Browse files- app/streamlit_app.py +100 -156
app/streamlit_app.py
CHANGED
|
@@ -19,192 +19,136 @@ 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
|
| 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"""
|
| 30 |
if is_fraud:
|
| 31 |
fraud_database = [
|
| 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 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 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,
|
| 61 |
-
st.session_state.t_amount = random.uniform(5,
|
| 62 |
-
for i in range(1,
|
| 63 |
-
st.session_state[f"v_{i}"] = random.uniform(-1
|
| 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 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
if page == "Prediction":
|
| 84 |
-
st.markdown("""
|
| 85 |
-
<div style='text-align: center; padding: 1rem 0;'>
|
| 86 |
-
<h1 style='color: #1E3A8A;'>๐ณ Fraud Guard Intelligence</h1>
|
| 87 |
-
<p style='color: #6B7280; font-size: 1.2rem;'>Real-Time Transaction Risk Analysis</p>
|
| 88 |
-
</div>
|
| 89 |
-
""", unsafe_allow_html=True)
|
| 90 |
|
| 91 |
-
|
|
|
|
|
|
|
| 92 |
|
| 93 |
with col1:
|
| 94 |
-
st.
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
generate_sample(is_fraud=False)
|
| 99 |
-
with demo_col2:
|
| 100 |
-
if st.button("๐จ Simulate Fraud Attack", type="primary", use_container_width=True):
|
| 101 |
-
generate_sample(is_fraud=True)
|
| 102 |
-
|
| 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 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
|
|
|
|
|
|
|
|
|
| 124 |
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
|
|
|
|
|
|
|
|
|
| 128 |
try:
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
if pred == 1:
|
| 137 |
-
st.error(f"๐จ FRAUD DETECTED: {action}")
|
| 138 |
else:
|
| 139 |
-
st.success(
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
m_col1.metric("Risk Level", f"{prob:.4%}")
|
| 143 |
-
m_col2.metric("Prediction Output", pred)
|
| 144 |
-
|
| 145 |
st.progress(float(prob))
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
st.
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
shap.plots.waterfall(shap_values[0], show=False)
|
| 153 |
st.pyplot(fig)
|
| 154 |
|
| 155 |
except Exception as e:
|
| 156 |
-
st.error(
|
| 157 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 158 |
elif page == "Drift Monitoring":
|
| 159 |
-
|
| 160 |
-
st.
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 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 |
|
| 175 |
try:
|
| 176 |
from src.monitoring.drift import detect_drift
|
| 177 |
from src.pipeline.retrain_pipeline import retrain
|
| 178 |
except:
|
| 179 |
-
st.error("
|
| 180 |
st.stop()
|
| 181 |
|
| 182 |
-
if st.button("
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 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 |
-
|
| 194 |
-
report_path = "reports/drift_report.html"
|
| 195 |
-
if os.path.exists(report_path):
|
| 196 |
-
with open(report_path, "r", encoding="utf-8") as f:
|
| 197 |
-
html = f.read()
|
| 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}")
|
|
|
|
| 19 |
|
| 20 |
st.set_page_config(page_title="Fraud Guard", layout="wide")
|
| 21 |
|
| 22 |
+
# --- Initialize Session State ---
|
| 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 |
if is_fraud:
|
| 30 |
fraud_database = [
|
| 31 |
+
{"Time": 406.0, "Amount": 0.00, "V": [-2.312,1.951,-1.609,3.997,-0.522,-1.426,-2.537,1.391,-2.770,-2.772,3.202,-2.899,-0.595,-4.289,0.389,-1.140,-2.830,-0.016,0.416,0.126,0.517,-0.035,-0.465,0.320,0.044,0.177,0.261,-0.143]}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
]
|
| 33 |
+
chosen = random.choice(fraud_database)
|
| 34 |
+
st.session_state.t_time = chosen["Time"]
|
| 35 |
+
st.session_state.t_amount = chosen["Amount"]
|
| 36 |
+
for i in range(1,29):
|
| 37 |
+
st.session_state[f"v_{i}"] = chosen["V"][i-1]
|
|
|
|
|
|
|
|
|
|
| 38 |
else:
|
| 39 |
+
st.session_state.t_time = random.uniform(100,150000)
|
| 40 |
+
st.session_state.t_amount = random.uniform(5,150)
|
| 41 |
+
for i in range(1,29):
|
| 42 |
+
st.session_state[f"v_{i}"] = random.uniform(-1,1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
|
| 44 |
+
# --- Sidebar Navigation ---
|
| 45 |
+
page = st.sidebar.selectbox("๐ Choose Section", ["Prediction", "Explainability (SHAP)", "Drift Monitoring"])
|
| 46 |
+
|
| 47 |
+
# =========================
|
| 48 |
+
# ๐น PREDICTION PAGE (NO SHAP)
|
| 49 |
+
# =========================
|
| 50 |
if page == "Prediction":
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
|
| 52 |
+
st.markdown("## ๐ณ Fraud Guard Intelligence")
|
| 53 |
+
|
| 54 |
+
col1, col2 = st.columns([1,2])
|
| 55 |
|
| 56 |
with col1:
|
| 57 |
+
if st.button("Simulate Normal"):
|
| 58 |
+
generate_sample(False)
|
| 59 |
+
if st.button("Simulate Fraud"):
|
| 60 |
+
generate_sample(True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
|
| 62 |
+
with st.form("form"):
|
| 63 |
+
t_time = st.slider("Time",0.0,172800.0,key="t_time")
|
| 64 |
+
t_amount = st.slider("Amount",0.0,5000.0,key="t_amount")
|
| 65 |
+
|
| 66 |
+
v_data = {}
|
| 67 |
+
for i in range(1,29):
|
| 68 |
+
v_data[f"V{i}"] = st.number_input(f"V{i}",key=f"v_{i}")
|
| 69 |
|
| 70 |
+
threshold = st.slider("Threshold",0.05,0.95,0.15)
|
| 71 |
+
submit = st.form_submit_button("Predict")
|
| 72 |
+
|
| 73 |
+
with col2:
|
| 74 |
+
if submit:
|
| 75 |
+
payload = {"Time":st.session_state.t_time,"Amount":st.session_state.t_amount,**v_data}
|
| 76 |
try:
|
| 77 |
+
res = requests.post(API_URL,json=payload).json()
|
| 78 |
+
prob = res["fraud_probability"]
|
| 79 |
+
pred = 1 if prob>threshold else 0
|
| 80 |
+
|
| 81 |
+
if pred:
|
| 82 |
+
st.error("๐จ FRAUD")
|
|
|
|
|
|
|
|
|
|
| 83 |
else:
|
| 84 |
+
st.success("โ
SAFE")
|
| 85 |
+
|
| 86 |
+
st.metric("Risk",f"{prob:.4%}")
|
|
|
|
|
|
|
|
|
|
| 87 |
st.progress(float(prob))
|
| 88 |
+
|
| 89 |
+
# ๐ฅ Store payload for SHAP page
|
| 90 |
+
st.session_state["last_payload"] = payload
|
| 91 |
+
|
| 92 |
+
except Exception as e:
|
| 93 |
+
st.error(e)
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
# =========================
|
| 97 |
+
# ๐น SHAP PAGE (SEPARATE)
|
| 98 |
+
# =========================
|
| 99 |
+
elif page == "Explainability (SHAP)":
|
| 100 |
+
|
| 101 |
+
st.title("๐ง Explainable AI (SHAP)")
|
| 102 |
+
|
| 103 |
+
if "last_payload" not in st.session_state:
|
| 104 |
+
st.warning("Run prediction first")
|
| 105 |
+
else:
|
| 106 |
+
if st.button("Generate SHAP Explanation"):
|
| 107 |
+
try:
|
| 108 |
+
payload = st.session_state["last_payload"]
|
| 109 |
+
|
| 110 |
+
input_df = pd.DataFrame([payload])
|
| 111 |
+
|
| 112 |
+
pipeline = PredictPipeline()
|
| 113 |
+
processed = pipeline.preprocess(input_df)
|
| 114 |
+
|
| 115 |
+
explainer = ShapExplainer()
|
| 116 |
+
shap_values = explainer.explain(processed)
|
| 117 |
+
|
| 118 |
+
fig, ax = plt.subplots(figsize=(8,4))
|
| 119 |
shap.plots.waterfall(shap_values[0], show=False)
|
| 120 |
st.pyplot(fig)
|
| 121 |
|
| 122 |
except Exception as e:
|
| 123 |
+
st.error(e)
|
| 124 |
|
| 125 |
+
|
| 126 |
+
# =========================
|
| 127 |
+
# ๐น DRIFT PAGE (UNCHANGED)
|
| 128 |
+
# =========================
|
| 129 |
elif page == "Drift Monitoring":
|
| 130 |
+
|
| 131 |
+
st.title("๐ Drift Monitoring")
|
| 132 |
+
|
| 133 |
+
if st.button("Inject Drift"):
|
| 134 |
+
for _ in range(50):
|
| 135 |
+
data={"Time":random.uniform(10,50000),"Amount":random.uniform(1000,5000)}
|
| 136 |
+
for i in range(1,29):
|
| 137 |
+
data[f"V{i}"]=random.uniform(-15,15)
|
| 138 |
+
save_to_db(data,1,0.99)
|
| 139 |
+
st.success("Injected")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
|
| 141 |
try:
|
| 142 |
from src.monitoring.drift import detect_drift
|
| 143 |
from src.pipeline.retrain_pipeline import retrain
|
| 144 |
except:
|
| 145 |
+
st.error("Not supported")
|
| 146 |
st.stop()
|
| 147 |
|
| 148 |
+
if st.button("Run Drift"):
|
| 149 |
+
detect_drift("data/creditcard.csv")
|
| 150 |
+
st.success("Done")
|
| 151 |
+
|
| 152 |
+
if st.button("Retrain"):
|
| 153 |
+
retrain()
|
| 154 |
+
st.success("Retrained")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|