CrimeManagementSystem / frontend.py
Chaitanya895's picture
Upload 15 files
27de439 verified
import streamlit as st
import pandas as pd
import requests
import plotly.express as px
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from xgboost import XGBClassifier
from sklearn.preprocessing import LabelEncoder
from sklearn.metrics import accuracy_score, f1_score
import sqlite3
import base64
import logging
import os
import numpy as np
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Database connection
DB_PATH = "crime_records.db"
def get_db_connection():
if not os.path.exists(DB_PATH):
logger.error(f"Database file {DB_PATH} does not exist")
raise Exception(f"Database file {DB_PATH} not found. Run init_db.py to create it.")
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
return conn
# Data analysis function
def load_crime_data(search_query=""):
try:
conn = get_db_connection()
query = "SELECT LOWER(crime_type) AS crime_type, LOWER(description) AS description, LOWER(location) AS location, date, LOWER(officer_in_charge) AS officer_in_charge, LOWER(status) AS status FROM Crimes"
if search_query:
query += " WHERE LOWER(crime_type) LIKE ? OR LOWER(location) LIKE ?"
df = pd.read_sql(query, conn, params=(f"%{search_query.lower()}%", f"%{search_query.lower()}%"))
else:
df = pd.read_sql(query, conn)
conn.close()
if df.empty:
logger.warning("No data found in Crimes table")
st.warning("No crime data available. Please add crimes via 'Add Crime'.")
return df
except Exception as e:
logger.error(f"Error loading crime data: {e}")
st.error(f"Error loading crime data: {e}. Ensure the database and Crimes table are initialized.")
return pd.DataFrame()
# ML model training and prediction
def train_ml_models():
df = load_crime_data()
if df.empty or len(df) < 5:
logger.warning("Insufficient data for ML training")
st.error("Insufficient data for ML training. Please add at least 5 crime records.")
return None, None, None, None, None, 0, 0, 0, 0
df = df.dropna()
if len(df['status'].unique()) < 2:
logger.warning("Only one status value found; ML models require multiple classes")
st.error("ML models require at least two different status values (e.g., 'open' and 'closed').")
return None, None, None, None, None, 0, 0, 0, 0
le_crime = LabelEncoder()
le_location = LabelEncoder()
le_status = LabelEncoder()
df['crime_type_encoded'] = le_crime.fit_transform(df['crime_type'])
df['location_encoded'] = le_location.fit_transform(df['location'])
df['status_encoded'] = le_status.fit_transform(df['status'])
features = ['crime_type_encoded', 'location_encoded']
X = df[features]
y = df['status_encoded']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Random Forest
rf_model = RandomForestClassifier(n_estimators=100, random_state=42)
rf_model.fit(X_train, y_train)
rf_pred = rf_model.predict(X_test)
rf_accuracy = accuracy_score(y_test, rf_pred)
rf_f1 = f1_score(y_test, rf_pred, average='weighted')
# XGBoost
xgb_model = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', random_state=42)
xgb_model.fit(X_train, y_train)
xgb_pred = xgb_model.predict(X_test)
xgb_accuracy = accuracy_score(y_test, xgb_pred)
xgb_f1 = f1_score(y_test, xgb_pred, average='weighted')
return rf_model, xgb_model, le_crime, le_location, le_status, rf_accuracy, rf_f1, xgb_accuracy, xgb_f1
# Function to set modern styling with background image and white text
def set_background_and_text(image_file):
try:
with open(image_file, "rb") as image:
encoded_image = base64.b64encode(image.read()).decode()
css = f"""
<style>
@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;700&display=swap');
@import url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css');
.stApp {{
background-image: url("data:image/png;base64,{encoded_image}");
background-size: cover;
background-position: center;
background-repeat: no-repeat;
background-attachment: fixed;
font-family: 'Montserrat', sans-serif;
color: white !important;
}}
.stApp > div {{
background-color: rgba(0, 0, 0, 0.7);
border-radius: 15px;
padding: 20px;
margin: 10px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.5);
}}
.stApp * {{
color: white !important;
}}
.stApp h1, .stApp h2, .stApp h3, .stApp h4, .stApp h5, .stApp h6 {{
color: #00b7eb !important;
font-weight: 700;
}}
.stApp input, .stApp select, .stApp textarea {{
color: white !important;
background-color: rgba(30, 30, 30, 0.8) !important;
border: 2px solid #00b7eb !important;
border-radius: 8px !important;
padding: 8px;
transition: border-color 0.3s ease;
}}
.stApp input:focus, .stApp select:focus, .stApp textarea:focus {{
border-color: #ff073a !important;
box-shadow: 0 0 8px rgba(255, 7, 58, 0.5);
}}
.stApp .stButton>button {{
color: white !important;
background-color: #00b7eb !important;
border: 2px solid #00b7eb !important;
border-radius: 8px;
padding: 12px 24px;
font-weight: 500;
font-size: 16px;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 8px;
}}
.stApp .stButton>button:hover {{
background-color: #ff073a !important;
border-color: #ff073a !important;
transform: translateY(-2px);
box-shadow: 0 4px 10px rgba(255, 7, 58, 0.3);
}}
.stApp .stDataFrame, .stApp table {{
color: white !important;
background-color: rgba(30, 30, 30, 0.8) !important;
border-radius: 8px;
border: 1px solid #00b7eb;
}}
.stApp .stMarkdown p, .stApp .stMarkdown div {{
color: white !important;
}}
.stApp .stSelectbox > div > div > div {{
color: white !important;
background-color: rgba(30, 30, 30, 0.8) !important;
border: 2px solid #00b7eb !important;
border-radius: 8px;
}}
.stSidebar {{
background-color: rgba(20, 20, 30, 0.9) !important;
border-right: 2px solid #00b7eb;
}}
.stSidebar * {{
color: white !important;
}}
.stSidebar .stSelectbox > div > div > div {{
background-color: rgba(30, 30, 30, 0.8) !important;
border: 2px solid #00b7eb !important;
border-radius: 8px;
}}
.stPlotlyChart {{
background-color: rgba(30, 30, 30, 0.8) !important;
border-radius: 8px;
padding: 10px;
}}
.search-container {{
display: flex;
align-items: center;
gap: 10px;
}}
.search-container input {{
flex-grow: 1;
}}
.fa-icon {{
margin-right: 8px;
}}
.prediction-output {{
background-color: rgba(30, 30, 30, 0.9);
border: 2px solid #00b7eb;
border-radius: 8px;
padding: 15px;
margin-top: 20px;
}}
</style>
"""
st.markdown(css, unsafe_allow_html=True)
logger.info(f"Modern styling and background image set for {image_file}")
except FileNotFoundError:
logger.error(f"Background image {image_file} not found")
st.warning(f"Background image for {image_file} not found.")
css = """
<style>
@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;700&display=swap');
@import url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css');
.stApp {
font-family: 'Montserrat', sans-serif;
color: white !important;
background-color: #1a1a1a;
}
.stApp > div {
background-color: rgba(0, 0, 0, 0.7);
border-radius: 15px;
padding: 20px;
margin: 10px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.5);
}
.stApp * {
color: white !important;
}
.stApp h1, .stApp h2, .stApp h3, .stApp h4, .stApp h5, .stApp h6 {
color: #00b7eb !important;
font-weight: 700;
}
.stApp input, .stApp select, .stApp textarea {
color: white !important;
background-color: rgba(30, 30, 30, 0.8) !important;
border: 2px solid #00b7eb !important;
border-radius: 8px !important;
padding: 8px;
transition: border-color 0.3s ease;
}
.stApp input:focus, .stApp select:focus, .stApp textarea:focus {
border-color: #ff073a !important;
box-shadow: 0 0 8px rgba(255, 7, 58, 0.5);
}
.stApp .stButton>button {
color: white !important;
background-color: #00b7eb !important;
border: 2px solid #00b7eb !important;
border-radius: 8px;
padding: 12px 24px;
font-weight: 500;
font-size: 16px;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 8px;
}
.stApp .stButton>button:hover {
background-color: #ff073a !important;
border-color: #ff073a !important;
transform: translateY(-2px);
box-shadow: 0 4px 10px rgba(255, 7, 58, 0.3);
}
.stApp .stDataFrame, .stApp table {
color: white !important;
background-color: rgba(30, 30, 30, 0.8) !important;
border-radius: 8px;
border: 1px solid #00b7eb;
}
.stApp .stMarkdown p, .stApp .stMarkdown div {
color: white !important;
}
.stApp .stSelectbox > div > div > div {
color: white !important;
background-color: rgba(30, 30, 30, 0.8) !important;
border: 2px solid #00b7eb !important;
border-radius: 8px;
}
.stSidebar {
background-color: rgba(20, 20, 30, 0.9) !important;
border-right: 2px solid #00b7eb;
}
.stSidebar * {
color: white !important;
}
.stSidebar .stSelectbox > div > div > div {
background-color: rgba(30, 30, 30, 0.8) !important;
border: 2px solid #00b7eb !important;
border-radius: 8px;
}
.stPlotlyChart {
background-color: rgba(30, 30, 30, 0.8) !important;
border-radius: 8px;
padding: 10px;
}
.search-container {
display: flex;
align-items: center;
gap: 10px;
}
.search-container input {
flex-grow: 1;
}
.fa-icon {
margin-right: 8px;
}
.prediction-output {
background-color: rgba(30, 30, 30, 0.9);
border: 2px solid #00b7eb;
border-radius: 8px;
padding: 15px;
margin-top: 20px;
}
</style>
"""
st.markdown(css, unsafe_allow_html=True)
# Initialize session state for user authentication
if 'user' not in st.session_state:
st.session_state.user = None
st.session_state.role = None
if 'page' not in st.session_state:
st.session_state.page = "Login"
# Role-based menu options
ROLE_MENUS = {
"admin": ["Dashboard", "Add Crime", "View Crimes", "Add FIR", "View FIRs", "Data Analysis", "ML Predictions", "Sign Up", "Login", "Logout"],
"police": ["Dashboard", "View Crimes", "Add FIR", "View FIRs", "ML Predictions", "Login", "Logout"],
None: ["Login", "Sign Up"]
}
# Sidebar navigation
st.sidebar.header("User Authentication")
auth_choice = st.sidebar.selectbox("Action", ["Login", "Sign Up", "Logout"] if st.session_state.user else ["Login", "Sign Up"], key="auth_choice")
# Signup
if auth_choice == "Sign Up":
st.header("Sign Up")
username = st.text_input("Username", help="Enter a unique username")
password = st.text_input("Password", type="password", help="Enter a secure password")
role = st.selectbox("Role", ["admin", "police"], help="Select user role: admin or police")
if st.button("<i class='fas fa-user-plus fa-icon'></i> Register", key="signup", help="Create a new user account"):
if username and password:
try:
response = requests.post("http://localhost:8000/api/users", json={"username": username, "password": password, "role": role})
response.raise_for_status()
st.success("User registered successfully! Please log in.")
st.session_state.page = "Login"
except requests.RequestException as e:
st.error(f"Error: {e}")
else:
st.error("Please fill in all fields.")
# Login
elif auth_choice == "Login":
st.header("Login")
username = st.text_input("Username", help="Enter your username")
password = st.text_input("Password", type="password", help="Enter your password")
if st.button("<i class='fas fa-sign-in-alt fa-icon'></i> Login", key="login", help="Log in to the system"):
if username and password:
try:
response = requests.post("http://localhost:8000/api/login", json={"username": username, "password": password})
response.raise_for_status()
data = response.json()
st.session_state.user = data["username"]
st.session_state.role = data["role"]
st.success(f"Logged in as {data['username']} ({data['role']})")
st.session_state.page = "Dashboard"
except requests.RequestException as e:
st.error(f"Error: {e}")
else:
st.error("Please fill in all fields.")
# Logout
elif auth_choice == "Logout":
st.session_state.user = None
st.session_state.role = None
st.session_state.page = "Login"
st.success("Logged out successfully!")
st.rerun()
# Set background image and modern styling
background_images = {
"Dashboard": "static\mainmenu.jpg",
"Add Crime": "static\AddCrimes.jpg",
"View Crimes": "static\ViewCrimes.jpg",
"Add FIR": "static\AddFirs.jpg",
"View FIRs": "static\ViewFirs.jpg",
"Data Analysis": "static\mainmenu.jpg",
"ML Predictions": "static\AddCrimes.jpg",
"Sign Up": "static\ViewCrimes.jpg",
"Login": "static\ViewFirs.jpg"
}
# Apply background and styling
if st.session_state.page in background_images:
set_background_and_text(background_images[st.session_state.page])
else:
set_background_and_text(None)
# Main menu navigation (only shown if logged in)
if st.session_state.user:
st.sidebar.header("Navigation")
menu = ROLE_MENUS[st.session_state.role]
choice = st.sidebar.selectbox("Menu", menu, key="main_menu")
else:
choice = st.session_state.page
# Main app logic
if choice == "Dashboard" and st.session_state.user:
st.header("Dashboard")
st.write(f"Welcome, {st.session_state.user} ({st.session_state.role.title()})! Navigate using the sidebar.")
elif choice == "Add Crime" and st.session_state.user and st.session_state.role == "admin":
st.header("Add Crime")
crime_type = st.text_input("Crime Type", help="e.g., Cyber Crimes, Theft, Assault")
description = st.text_area("Description", help="Brief description of the crime")
location = st.text_input("Location", help="e.g., New York, Chicago")
date = st.date_input("Date")
officer = st.text_input("Officer In Charge", help="Name of the assigned officer")
if st.button("<i class='fas fa-save fa-icon'></i> Submit Crime", key="add_crime", help="Save the crime record"):
if crime_type and description and location and officer:
crime_data = {
"crime_type": crime_type.lower(),
"description": description.lower(),
"location": location.lower(),
"date": str(date),
"officer_in_charge": officer.lower()
}
try:
response = requests.post("http://localhost:8000/api/crimes", json=crime_data)
response.raise_for_status()
st.success("Crime added successfully!")
except requests.RequestException as e:
st.error(f"Error: {e}")
else:
st.error("Please fill in all fields.")
elif choice == "View Crimes" and st.session_state.user:
st.header("View Crimes")
with st.container():
st.markdown('<div class="search-container">', unsafe_allow_html=True)
search_query = st.text_input("Search Crimes", placeholder="Search by crime type or location...", help="e.g., Cyber Crimes, New York")
if st.button("<i class='fas fa-search fa-icon'></i> Search", key="search_crimes"):
try:
response = requests.get("http://localhost:8000/api/crimes", params={"search": search_query})
response.raise_for_status()
crimes = response.json()
df = pd.DataFrame(crimes)
for col in ['crime_type', 'description', 'location', 'officer_in_charge', 'status']:
if col in df.columns:
df[col] = df[col].str.title()
st.dataframe(df)
except requests.RequestException as e:
st.error(f"Error loading crimes: {e}")
st.markdown('</div>', unsafe_allow_html=True)
elif choice == "Add FIR" and st.session_state.user:
st.header("Add FIR")
crime_id = st.number_input("Crime ID", min_value=1, step=1, help="ID of the associated crime")
complainant_name = st.text_input("Complainant Name", help="Name of the person filing the FIR")
complainant_contact = st.text_input("Complainant Contact", help="Email or phone number")
filing_date = st.date_input("Filing Date")
if st.button("<i class='fas fa-save fa-icon'></i> Submit FIR", key="add_fir", help="Save the FIR record"):
if complainant_name and complainant_contact:
fir_data = {
"crime_id": crime_id,
"complainant_name": complainant_name.lower(),
"complainant_contact": complainant_contact.lower(),
"filing_date": str(filing_date)
}
try:
response = requests.post("http://localhost:8000/api/firs", json=fir_data)
response.raise_for_status()
st.success("FIR added successfully!")
except requests.RequestException as e:
st.error(f"Error: {e}")
else:
st.error("Please fill in all fields.")
elif choice == "View FIRs" and st.session_state.user:
st.header("View FIRs")
with st.container():
st.markdown('<div class="search-container">', unsafe_allow_html=True)
search_query = st.text_input("Search FIRs", placeholder="Search by complainant name or contact...", help="e.g., Alice Brown, alice@example.com")
if st.button("<i class='fas fa-search fa-icon'></i> Search", key="search_firs"):
try:
response = requests.get("http://localhost:8000/api/firs", params={"search": search_query})
response.raise_for_status()
firs = response.json()
df = pd.DataFrame(firs)
for col in ['complainant_name', 'complainant_contact']:
if col in df.columns:
df[col] = df[col].str.title()
st.dataframe(df)
except requests.RequestException as e:
st.error(f"Error loading FIRs: {e}")
st.markdown('</div>', unsafe_allow_html=True)
elif choice == "Data Analysis" and st.session_state.user and st.session_state.role == "admin":
st.header("Data Analysis")
with st.container():
st.markdown('<div class="search-container">', unsafe_allow_html=True)
search_query = st.text_input("Filter Crimes", placeholder="Filter by crime type or location...", help="e.g., Cyber Crimes, New York")
if st.button("<i class='fas fa-filter fa-icon'></i> Filter", key="filter_analysis"):
df = load_crime_data(search_query)
else:
df = load_crime_data()
st.markdown('</div>', unsafe_allow_html=True)
if not df.empty:
st.subheader("Crime Type Distribution")
fig = px.histogram(df, x="crime_type", title="Distribution of Crime Types")
fig.update_layout(
title_font_color="white",
xaxis_title_font_color="white",
yaxis_title_font_color="white",
font_color="white",
plot_bgcolor="rgba(0,0,0,0)",
paper_bgcolor="rgba(0,0,0,0)"
)
st.plotly_chart(fig)
st.subheader("Crime Trends Over Time")
df['date'] = pd.to_datetime(df['date'])
df['year_month'] = df['date'].dt.to_period('M')
trend_data = df.groupby('year_month').size().reset_index(name='count')
trend_data['year_month'] = trend_data['year_month'].astype(str)
fig = px.line(trend_data, x='year_month', y='count', title="Crime Trends Over Time")
fig.update_layout(
title_font_color="white",
xaxis_title_font_color="white",
yaxis_title_font_color="white",
font_color="white",
plot_bgcolor="rgba(0,0,0,0)",
paper_bgcolor="rgba(0,0,0,0)"
)
st.plotly_chart(fig)
else:
st.error("No crime data available for analysis. Please add crimes or check the database.")
elif choice == "ML Predictions" and st.session_state.user:
st.header("Predict Crime Status")
st.markdown("""
This feature uses advanced machine learning models (Random Forest and XGBoost) to predict whether a crime case is likely to be 'Open' or 'Closed' based on its type and location.
The prediction can help prioritize investigations or allocate resources effectively. Select a model, crime type, and location, then click Predict to see the result.
""")
try:
rf_model, xgb_model, le_crime, le_location, le_status, rf_accuracy, rf_f1, xgb_accuracy, xgb_f1 = train_ml_models()
if rf_model is None:
st.error("No data available for ML predictions. Please add at least 5 crime records with varied statuses (e.g., 'open' and 'closed').")
else:
# Model Performance
st.subheader("Model Performance")
st.write(f"**Random Forest**: Accuracy: {rf_accuracy:.2f}, F1-Score: {rf_f1:.2f} (measures model reliability)")
st.write(f"**XGBoost**: Accuracy: {xgb_accuracy:.2f}, F1-Score: {xgb_f1:.2f} (measures model reliability)")
st.markdown("**Accuracy**: Percentage of correct predictions. **F1-Score**: Balances precision and recall for robust evaluation.")
# Training Data Preview
st.subheader("Training Data Preview")
df = load_crime_data()
if not df.empty:
preview_df = df[['crime_type', 'location', 'status']].copy()
preview_df.columns = ['Crime Type', 'Location', 'Status']
preview_df = preview_df.apply(lambda x: x.str.title() if x.dtype == "object" else x)
st.dataframe(preview_df, height=200)
else:
st.warning("No data available to display.")
# Prediction Inputs
st.subheader("Make a Prediction")
model_choice = st.selectbox("Select Model", ["Random Forest", "XGBoost"], help="Choose the machine learning model for prediction")
crime_types = sorted(set(load_crime_data()['crime_type']))
locations = sorted(set(load_crime_data()['location']))
if len(crime_types) == 0 or len(locations) == 0:
st.error("No crime data available for prediction. Please add crimes via 'Add Crime'.")
else:
crime_type = st.selectbox("Crime Type", [t.title() for t in crime_types], help="e.g., Cyber Crimes, Theft")
location = st.selectbox("Location", [t.title() for t in locations], help="e.g., New York, Chicago")
if st.button("<i class='fas fa-brain fa-icon'></i> Predict", key="predict", help="Predict the crime status"):
crime_type_encoded = le_crime.transform([crime_type.lower()])[0]
location_encoded = le_location.transform([location.lower()])[0]
model = rf_model if model_choice == "Random Forest" else xgb_model
input_data = np.array([[crime_type_encoded, location_encoded]])
prediction = model.predict(input_data)[0]
status = le_status.inverse_transform([prediction])[0]
# Get prediction probabilities
probs = model.predict_proba(input_data)[0]
prob_dict = {le_status.inverse_transform([i])[0].title(): f"{prob*100:.1f}%" for i, prob in enumerate(probs)}
# Get feature importance
feature_names = ['Crime Type', 'Location']
if model_choice == "Random Forest":
importance = model.feature_importances_
else: # XGBoost
importance = model.feature_importances_
importance_dict = {name: f"{imp*100:.1f}%" for name, imp in zip(feature_names, importance)}
# Display results in a styled container
st.markdown('<div class="prediction-output">', unsafe_allow_html=True)
st.write(f"**Predicted Status**: {status.title()}")
st.write("**Confidence Scores**:")
for status, prob in prob_dict.items():
st.write(f"- {status}: {prob}")
st.write("**Feature Importance**: (How much each input affects the prediction)")
for name, imp in importance_dict.items():
st.write(f"- {name}: {imp}")
st.markdown('</div>', unsafe_allow_html=True)
except Exception as e:
st.error(f"Error in ML Predictions: {e}. Ensure the database is initialized with sufficient data.")
elif choice in ["Add Crime", "Data Analysis"] and st.session_state.user and st.session_state.role != "admin":
st.error("Access Denied: This feature is restricted to admin users.")
elif choice not in ["Login", "Sign Up"] and not st.session_state.user:
st.error("Please log in to access this feature.")