import streamlit as st
import pandas as pd
import numpy as np
import plotly.graph_objects as go
import plotly.express as px
from datetime import datetime
import time
import json
import logging
from typing import Dict, Union
from pathlib import Path
from src.utils.model_utils import ModelPredictor
from src.utils.data_preprocessing import validate_input_data
from src.utils import monitoring
# --- Basic Setup ---
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
st.set_page_config(
page_title="Advanced Fraud Detection System",
page_icon="🛡️",
layout="wide",
initial_sidebar_state="expanded"
)
# --- Load CSS from external file ---
def load_css(file_path: Path):
with open(file_path) as f:
st.markdown(f"", unsafe_allow_html=True)
# --- Model & State Management ---
@st.cache_resource
def get_predictor():
"""Cached function to load the model predictor once."""
try:
predictor = ModelPredictor()
return predictor
except Exception as e:
st.error(f"🚨 Critical Error: Could not load the model. Details: {e}")
return None
def create_sample_transaction() -> Dict:
"""Generates a random sample transaction for demonstration."""
np.random.seed(int(time.time()))
sample = {'Time': np.random.randint(0, 172800), 'Amount': round(np.random.uniform(1, 1000), 2)}
for i in range(1, 29):
sample[f'V{i}'] = np.random.normal(0, 1)
return sample
# --- UI Page Functions ---
def single_prediction_page(predictor: ModelPredictor):
st.markdown('
🔍 Real-Time Transaction Analysis
Analyze individual transactions for fraud patterns.
', unsafe_allow_html=True)
col1, col2 = st.columns([2, 1])
with col1:
st.markdown('', unsafe_allow_html=True)
st.subheader("💳 Transaction Input")
input_method = st.radio("Select input method:", ["✍️ Manual Entry", "🎲 Sample Transaction", "📋 JSON Import"], horizontal=True, key="input_method")
# Form for Manual Entry
if input_method == "✍️ Manual Entry":
with st.form("transaction_form"):
# Your form code remains the same...
st.markdown("**⏰ Timing & Amount**")
c1, c2 = st.columns(2)
time_val = c1.number_input("Time (seconds since first transaction)", min_value=0, value=3600, help="Time elapsed in seconds.")
amount_val = c2.number_input("Amount ($)", min_value=0.01, value=100.0, format="%.2f")
st.markdown("**🔢 Anonymized Features (V1-V28)**")
v_features = {}
with st.expander("Enter V1-V28 Features"):
cols = st.columns(4)
for i in range(1, 29):
with cols[(i-1)%4]:
v_features[f'V{i}'] = st.number_input(f"V{i}", value=0.0, format="%.6f", key=f"v{i}")
submitted = st.form_submit_button("🚀 Analyze Transaction", use_container_width=True, type="primary")
if submitted:
st.session_state.transaction_data = {'Time': time_val, 'Amount': amount_val, **v_features}
st.session_state.analysis_triggered = True
# Logic for other input methods
elif input_method == "🎲 Sample Transaction":
if st.button("🎲 Generate & Analyze Sample", use_container_width=True, type="primary"):
st.session_state.transaction_data = create_sample_transaction()
st.session_state.analysis_triggered = True
elif input_method == "📋 JSON Import":
json_input = st.text_area("Paste JSON data here:", height=250, placeholder='{"Time": 3600, "Amount": 100.0, ...}')
if st.button("🔍 Process JSON", use_container_width=True, type="primary"):
if json_input:
try:
st.session_state.transaction_data = json.loads(json_input)
st.session_state.analysis_triggered = True
except json.JSONDecodeError:
st.error("❌ Invalid JSON format.")
st.markdown('
', unsafe_allow_html=True)
with col2:
st.markdown('', unsafe_allow_html=True)
st.subheader("🎯 Analysis Results")
if st.session_state.get('analysis_triggered', False):
transaction_data = st.session_state.transaction_data
if not validate_input_data(transaction_data):
st.error("❌ Invalid input data. Please check all required fields.")
else:
try:
with st.spinner("🧠 AI analyzing transaction..."):
start_time = time.time()
prediction, probability = predictor.predict_single(transaction_data, use_optimal_threshold=True)
processing_time = time.time() - start_time
monitoring.log_prediction(transaction_data, prediction, probability, processing_time)
if prediction == 1:
st.markdown('
🚨 FRAUD DETECTED 🚨
', unsafe_allow_html=True)
else:
st.markdown('
✅ LEGITIMATE TRANSACTION
', unsafe_allow_html=True)
# ENHANCEMENT: Add Risk Level and Gauge Chart
if probability > 0.8: risk_level, risk_color = "High", "red"
elif probability > 0.5: risk_level, risk_color = "Medium", "orange"
else: risk_level, risk_color = "Low", "green"
st.metric("⚠️ Risk Level", risk_level)
fig = go.Figure(go.Indicator(
mode = "gauge+number",
value = probability,
domain = {'x': [0, 1], 'y': [0, 1]},
title = {'text': "Fraud Score", 'font': {'size': 20, 'color': 'white'}},
number = {'suffix': "%", 'valueformat': '.1f', 'font': {'size': 30, 'color': 'white'}},
gauge = {
'axis': {'range': [None, 1], 'tickwidth': 1, 'tickcolor': "white", 'tickformat': '.0%'},
'bar': {'color': risk_color, 'thickness': 0.3},
'bgcolor': "#4A4A4A",
'steps': [
{'range': [0, 0.5], 'color': '#3A683B'},
{'range': [0.5, 0.8], 'color': '#75602A'},
{'range': [0.8, 1], 'color': '#753228'}],
'threshold': {
'line': {'color': "#FFD700", 'width': 3},
'thickness': 0.75,
'value': predictor.optimal_threshold
}}))
fig.update_layout(paper_bgcolor="rgba(0,0,0,0)", font={'color': "white"})
st.plotly_chart(fig, use_container_width=True)
st.caption(f"Model Threshold: {predictor.optimal_threshold:.2%}")
st.success(f"⚡ Processed in {processing_time*1000:.1f}ms")
except Exception as e:
st.error(f"💥 Analysis error: {e}")
# Reset the trigger
st.session_state.analysis_triggered = False
else:
st.info("👆 Enter transaction details and click analyze to see results.")
st.markdown('
', unsafe_allow_html=True)
def batch_processing_page(predictor: ModelPredictor):
st.markdown('📊 Bulk Transaction Processing
Upload a CSV file to process multiple transactions at once.
', unsafe_allow_html=True)
uploaded_file = st.file_uploader("CSV file must contain 'Time', 'Amount', and 'V1'-'V28' columns.", type=['csv'], key="batch_uploader")
if uploaded_file is not None:
try:
df = pd.read_csv(uploaded_file)
st.success(f"✅ File '{uploaded_file.name}' uploaded successfully with {len(df):,} rows.")
if st.button("🚀 Start Batch Processing", use_container_width=True, type="primary"):
if not validate_input_data(df):
st.error("❌ Invalid data format. Please check the file columns.")
return
progress_bar = st.progress(0, text="Starting batch processing...")
start_time = time.time()
predictions, probabilities = predictor.predict_batch(df, use_optimal_threshold=True)
df['Prediction'] = predictions
df['Fraud_Probability'] = probabilities
# Log results
total_time = time.time() - start_time
for index, row in df.iterrows():
row_processing_time = total_time / len(df) if len(df) > 0 else 0
monitoring.log_prediction(row.to_dict(), row['Prediction'], row['Fraud_Probability'], row_processing_time)
progress_bar.progress(100, text="✅ Batch processing complete!")
# Store results in session state to prevent re-running
st.session_state.batch_results = df
except Exception as e:
st.error(f"💥 An error occurred: {e}")
st.exception(e)
# ENHANCEMENT: Display a comprehensive results dashboard
if 'batch_results' in st.session_state:
st.markdown("---")
st.subheader("📊 Batch Processing Dashboard")
results_df = st.session_state.batch_results
fraud_count = results_df['Prediction'].sum()
fraud_amount = results_df[results_df['Prediction'] == 1]['Amount'].sum()
# Summary Metrics
col1, col2, col3, col4 = st.columns(4)
col1.metric("Processed", f"{len(results_df):,}")
col2.metric("🚨 Fraud Detected", f"{fraud_count:,}", f"{fraud_count/len(results_df):.2%}")
col3.metric("💸 Amount at Risk", f"${fraud_amount:,.2f}")
col4.metric("Total Amount", f"${results_df['Amount'].sum():,.2f}")
# Charts
c1, c2 = st.columns(2)
with c1:
fig_pie = px.pie(results_df, names='Prediction', title='Transaction Classification',
color_discrete_map={0: '#51cf66', 1: '#ff6b6b'},
labels={0: 'Legitimate', 1: 'Fraud'})
fig_pie.update_layout(paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)")
st.plotly_chart(fig_pie, use_container_width=True)
with c2:
fig_hist = px.histogram(results_df, x='Fraud_Probability', color='Prediction',
title='Distribution of Fraud Scores', nbins=50,
color_discrete_map={0: '#51cf66', 1: '#ff6b6b'})
fig_hist.update_layout(paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)")
st.plotly_chart(fig_hist, use_container_width=True)
# Download Buttons & Data Table
with st.expander("📥 Download Results & View Data", expanded=True):
display_cols = ['Time', 'Amount', 'Prediction', 'Fraud_Probability']
st.dataframe(results_df[display_cols].sort_values('Fraud_Probability', ascending=False), use_container_width=True)
c1, c2 = st.columns(2)
c1.download_button(
label="📊 Download Full Results (CSV)",
data=results_df.to_csv(index=False).encode('utf-8'),
file_name=f"full_results_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
mime='text/csv', use_container_width=True
)
c2.download_button(
label="🚨 Download Fraud Only (CSV)",
data=results_df[results_df['Prediction'] == 1].to_csv(index=False).encode('utf-8'),
file_name=f"fraud_only_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
mime='text/csv', use_container_width=True
)
def monitoring_page():
st.markdown('📈 Real-Time Session Monitoring
Track prediction patterns for your current session.
', unsafe_allow_html=True)
monitoring.initialize_monitor_state()
df_logs = monitoring.get_logs_as_dataframe()
if df_logs.empty:
st.info("📊 No predictions have been made in this session yet. Analyze a transaction to see monitoring data.")
return
# ENHANCEMENT: Add interactive charts to the monitoring page
st.markdown("---")
st.subheader("📊 Session Performance")
total_predictions = len(df_logs)
fraud_predictions = int(df_logs['prediction'].sum())
col1, col2, col3 = st.columns(3)
col1.metric("🔢 Total Predictions", f"{total_predictions:,}")
col2.metric("🚨 Fraud Detected", f"{fraud_predictions:,}")
col3.metric("⚡ Avg Response Time", f"{df_logs['processing_time_ms'].mean():.1f}ms")
c1, c2 = st.columns(2)
with c1:
df_logs['timestamp_hour'] = df_logs['timestamp'].dt.round('H')
hourly_counts = df_logs.groupby('timestamp_hour').size().reset_index(name='count')
fig_ts = px.line(hourly_counts, x='timestamp_hour', y='count', title='Prediction Volume Over Time', markers=True)
fig_ts.update_layout(paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)")
st.plotly_chart(fig_ts, use_container_width=True)
with c2:
fig_hist = px.histogram(df_logs, x='fraud_probability', title='Distribution of Fraud Scores (This Session)', nbins=25)
fig_hist.update_layout(paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)")
st.plotly_chart(fig_hist, use_container_width=True)
st.markdown("---")
st.subheader("📋 Recent Activity Log (This Session)")
st.dataframe(df_logs.sort_values(by="timestamp", ascending=False), use_container_width=True)
def main():
"""Main function to run the Streamlit app."""
css_path = Path(__file__).parent / "style.css"
load_css(css_path)
st.markdown('🛡️ Advanced Fraud Detection System
AI-Powered Transaction Security Analysis
', unsafe_allow_html=True)
predictor = get_predictor()
if predictor is None:
st.error("Model failed to load. The application cannot start.")
st.stop()
with st.sidebar:
st.markdown('', unsafe_allow_html=True)
page = st.radio("Select a page:", ["🔍 Single Prediction", "📊 Batch Processing", "📈 Model Monitoring"], label_visibility="collapsed")
if page == "🔍 Single Prediction":
single_prediction_page(predictor)
elif page == "📊 Batch Processing":
batch_processing_page(predictor)
elif page == "📈 Model Monitoring":
monitoring_page()
if __name__ == "__main__":
main()