|
|
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
|
|
|
|
|
|
|
|
|
logging.basicConfig(level=logging.INFO)
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
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')
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
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_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"]
|
|
|
}
|
|
|
|
|
|
|
|
|
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")
|
|
|
|
|
|
|
|
|
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.")
|
|
|
|
|
|
|
|
|
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.")
|
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
|
|
|
|
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"
|
|
|
}
|
|
|
|
|
|
|
|
|
if st.session_state.page in background_images:
|
|
|
set_background_and_text(background_images[st.session_state.page])
|
|
|
else:
|
|
|
set_background_and_text(None)
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
|
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.")
|
|
|
|
|
|
|
|
|
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.")
|
|
|
|
|
|
|
|
|
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]
|
|
|
|
|
|
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)}
|
|
|
|
|
|
feature_names = ['Crime Type', 'Location']
|
|
|
if model_choice == "Random Forest":
|
|
|
importance = model.feature_importances_
|
|
|
else:
|
|
|
importance = model.feature_importances_
|
|
|
importance_dict = {name: f"{imp*100:.1f}%" for name, imp in zip(feature_names, importance)}
|
|
|
|
|
|
|
|
|
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.") |