| | import streamlit as st |
| | import pandas as pd |
| | import numpy as np |
| | import os |
| | import xgboost as xgb |
| | import pickle |
| | import datetime |
| | from scipy.sparse import hstack, csr_matrix |
| | from groq import Groq |
| |
|
| | |
| | st.set_page_config( |
| | page_title="AI Crime Predictor", |
| | page_icon="π", |
| | layout="wide", |
| | ) |
| |
|
| | |
| | st.markdown(""" |
| | <style> |
| | |
| | /* Animated gradient background */ |
| | @keyframes gradientShift { |
| | 0% { background-position: 0% 50%; } |
| | 50% { background-position: 100% 50%; } |
| | 100% { background-position: 0% 50%; } |
| | } |
| | |
| | body, .stApp { |
| | background: linear-gradient(-45deg, #0a0e27, #1a1a2e, #16213e, #0f3460); |
| | background-size: 400% 400%; |
| | animation: gradientShift 15s ease infinite; |
| | color: #ffffff; |
| | } |
| | |
| | /* Title with gradient text */ |
| | .big-title { |
| | font-size: 3.5rem; |
| | font-weight: 800; |
| | background: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%); |
| | -webkit-background-clip: text; |
| | -webkit-text-fill-color: transparent; |
| | background-clip: text; |
| | text-align: center; |
| | margin-bottom: 10px; |
| | text-shadow: 0 0 30px rgba(102, 126, 234, 0.5); |
| | letter-spacing: -1px; |
| | } |
| | |
| | /* Subtitle with glow */ |
| | .sub-title { |
| | text-align: center; |
| | font-size: 1.3rem; |
| | color: #a8b2d1; |
| | margin-bottom: 40px; |
| | font-weight: 300; |
| | } |
| | |
| | /* Glassmorphism card */ |
| | .glass-card { |
| | background: rgba(255, 255, 255, 0.05); |
| | backdrop-filter: blur(10px); |
| | -webkit-backdrop-filter: blur(10px); |
| | padding: 30px; |
| | border-radius: 24px; |
| | border: 1px solid rgba(255, 255, 255, 0.1); |
| | box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37); |
| | transition: all 0.4s ease; |
| | margin-bottom: 25px; |
| | } |
| | |
| | .glass-card:hover { |
| | box-shadow: 0 12px 40px 0 rgba(102, 126, 234, 0.4); |
| | transform: translateY(-5px); |
| | border: 1px solid rgba(102, 126, 234, 0.3); |
| | } |
| | |
| | /* Premium button styling */ |
| | .stButton>button { |
| | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| | color: white; |
| | padding: 0.8rem 2rem; |
| | border-radius: 12px; |
| | border: none; |
| | font-size: 1.1rem; |
| | font-weight: 600; |
| | transition: all 0.3s ease; |
| | box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4); |
| | } |
| | |
| | .stButton>button:hover { |
| | background: linear-gradient(135deg, #764ba2 0%, #667eea 100%); |
| | transform: translateY(-2px) scale(1.02); |
| | box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6); |
| | } |
| | |
| | /* Sidebar styling */ |
| | [data-testid="stSidebar"] { |
| | background: rgba(15, 23, 42, 0.8); |
| | backdrop-filter: blur(10px); |
| | border-right: 1px solid rgba(255, 255, 255, 0.1); |
| | } |
| | |
| | /* Input fields */ |
| | .stTextInput>div>div>input, |
| | .stTextArea>div>div>textarea, |
| | .stNumberInput>div>div>input { |
| | background: rgba(255, 255, 255, 0.8) !important; |
| | border: 1px solid rgba(255, 255, 255, 0.3) !important; |
| | border-radius: 10px !important; |
| | color: #000000 !important; |
| | transition: all 0.3s ease; |
| | } |
| | |
| | /* Ensure text is visible when typing */ |
| | .stTextInput input, |
| | .stTextArea textarea { |
| | color: #000000 !important; |
| | } |
| | |
| | .stTextInput>div>div>input:focus, |
| | .stTextArea>div>div>textarea:focus, |
| | .stNumberInput>div>div>input:focus { |
| | border: 1px solid rgba(102, 126, 234, 0.8) !important; |
| | box-shadow: 0 0 15px rgba(102, 126, 234, 0.5) !important; |
| | color: #000000 !important; |
| | } |
| | |
| | /* Placeholder text styling */ |
| | .stTextInput input::placeholder, |
| | .stTextArea textarea::placeholder { |
| | color: rgba(0, 0, 0, 0.5) !important; |
| | } |
| | |
| | /* Chat message styles */ |
| | .user-message { |
| | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| | padding: 15px 20px; |
| | border-radius: 18px 18px 5px 18px; |
| | margin: 10px 0; |
| | max-width: 80%; |
| | margin-left: auto; |
| | color: white; |
| | font-size: 1rem; |
| | box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3); |
| | } |
| | |
| | .ai-message { |
| | background: rgba(255, 255, 255, 0.08); |
| | backdrop-filter: blur(10px); |
| | padding: 15px 20px; |
| | border-radius: 18px 18px 18px 5px; |
| | margin: 10px 0; |
| | max-width: 80%; |
| | margin-right: auto; |
| | color: #e2e8f0; |
| | font-size: 1rem; |
| | border: 1px solid rgba(255, 255, 255, 0.1); |
| | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); |
| | } |
| | |
| | /* Chat container */ |
| | .chat-container { |
| | background: rgba(255, 255, 255, 0.03); |
| | backdrop-filter: blur(10px); |
| | padding: 25px; |
| | border-radius: 20px; |
| | border: 1px solid rgba(255, 255, 255, 0.1); |
| | max-height: 500px; |
| | overflow-y: auto; |
| | margin-bottom: 20px; |
| | } |
| | |
| | /* Scrollbar styling */ |
| | .chat-container::-webkit-scrollbar { |
| | width: 8px; |
| | } |
| | |
| | .chat-container::-webkit-scrollbar-track { |
| | background: rgba(255, 255, 255, 0.05); |
| | border-radius: 10px; |
| | } |
| | |
| | .chat-container::-webkit-scrollbar-thumb { |
| | background: rgba(102, 126, 234, 0.5); |
| | border-radius: 10px; |
| | } |
| | |
| | .chat-container::-webkit-scrollbar-thumb:hover { |
| | background: rgba(102, 126, 234, 0.8); |
| | } |
| | |
| | /* Success/Info boxes */ |
| | .element-container div[data-testid="stMarkdownContainer"] > div[data-testid="stMarkdown"] { |
| | animation: fadeIn 0.5s ease; |
| | } |
| | |
| | @keyframes fadeIn { |
| | from { opacity: 0; transform: translateY(10px); } |
| | to { opacity: 1; transform: translateY(0); } |
| | } |
| | |
| | </style> |
| | """, unsafe_allow_html=True) |
| |
|
| | |
| | st.markdown('<p class="big-title">π AI Crime Prediction System</p>', unsafe_allow_html=True) |
| | st.markdown('<p class="sub-title">Predict crime category using time, location, and incident description.</p>', unsafe_allow_html=True) |
| |
|
| | |
| | @st.cache_resource |
| | def load_artifacts(): |
| | try: |
| | |
| | pkl_path = "src/crime_xgb_artifacts.pkl" |
| | with open(pkl_path, 'rb') as f: |
| | return pickle.load(f) |
| | except Exception as e: |
| | st.error(f"β Artifact loading error: {e}") |
| | return None |
| | artifacts = load_artifacts() |
| |
|
| | if not artifacts: |
| | st.warning("Artifacts missing! Add `crime_xgb_artifacts.pkl` in directory.") |
| | st.stop() |
| |
|
| | model = artifacts['model'] |
| | le_target = artifacts['le_target'] |
| | addr_hasher = artifacts['addr_hasher'] |
| | desc_hasher = artifacts['desc_hasher'] |
| | dense_cols = artifacts['dense_cols'] |
| |
|
| | |
| | @st.cache_resource |
| | def get_groq_client(): |
| | return Groq(api_key="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") |
| |
|
| | def explain_prediction_with_llama(prompt): |
| | """Use Groq's Llama model to explain crime prediction""" |
| | try: |
| | client = get_groq_client() |
| | chat_completion = client.chat.completions.create( |
| | messages=[ |
| | { |
| | "role": "user", |
| | "content": prompt, |
| | } |
| | ], |
| | model="llama-3.3-70b-versatile", |
| | ) |
| | return chat_completion.choices[0].message.content |
| | except Exception as e: |
| | return f"β οΈ Could not generate explanation: {e}" |
| |
|
| | |
| | st.sidebar.title("π Input Features") |
| |
|
| | date = st.sidebar.date_input("π
Date", datetime.date.today()) |
| | time = st.sidebar.time_input("β° Time", datetime.datetime.now().time()) |
| |
|
| | default_lat = 37.7749 |
| | default_lng = -122.4194 |
| |
|
| | lat = st.sidebar.number_input("π Latitude", value=default_lat, format="%.6f") |
| | lng = st.sidebar.number_input("π Longitude", value=default_lng, format="%.6f") |
| |
|
| | districts = sorted(['BAYVIEW', 'CENTRAL', 'INGLESIDE', 'MISSION', 'NORTHERN', 'PARK', 'RICHMOND', 'SOUTHERN', 'TARAVAL', 'TENDERLOIN']) |
| | district = st.sidebar.selectbox("π’ Police District", districts) |
| |
|
| | address = st.sidebar.text_input("π Address", "") |
| | description = st.sidebar.text_area("π Description", "") |
| |
|
| | |
| | with st.container(): |
| | st.markdown("<div class='glass-card'>", unsafe_allow_html=True) |
| |
|
| | st.subheader("π Prediction Panel") |
| |
|
| | if st.button("π Predict Crime Category"): |
| | try: |
| | dt_obj = pd.to_datetime(f"{date} {time}") |
| | hour = dt_obj.hour |
| | |
| | dense_data = { |
| | 'X': float(lng), |
| | 'Y': float(lat), |
| | 'Year': dt_obj.year, |
| | 'Month': dt_obj.month, |
| | 'Day': dt_obj.day, |
| | 'Minute': dt_obj.minute, |
| | 'Hour': hour, |
| | 'Hour_sin': np.sin(2 * np.pi * hour / 24), |
| | 'Hour_cos': np.cos(2 * np.pi * hour / 24), |
| | 'PdDistrict_enc': districts.index(district), |
| | 'DayOfWeek_enc': dt_obj.dayofweek |
| | } |
| |
|
| | dense_df = pd.DataFrame([dense_data])[dense_cols] |
| | dense_sparse = csr_matrix(dense_df.values) |
| |
|
| | addr_hashed = addr_hasher.transform([address.split()]) |
| | desc_hashed = desc_hasher.transform([description.split()]) |
| |
|
| | features = hstack([dense_sparse, addr_hashed, desc_hashed]) |
| |
|
| | probs = model.predict_proba(features)[0] |
| | top_idx = np.argmax(probs) |
| |
|
| | category = le_target.inverse_transform([top_idx])[0] |
| | confidence = probs[top_idx] * 100 |
| |
|
| | st.success(f"### π¨ Predicted Category: **{category}**") |
| | st.info(f"**Confidence:** {confidence:.2f}%") |
| |
|
| | |
| | top3 = probs.argsort()[-3:][::-1] |
| | chart_data = pd.DataFrame({ |
| | "Category": le_target.inverse_transform(top3), |
| | "Probability": probs[top3] |
| | }).set_index("Category") |
| |
|
| | st.subheader("π Top 3 Probabilities") |
| | st.bar_chart(chart_data) |
| |
|
| | st.subheader("π Location Preview") |
| | st.map(pd.DataFrame({"lat": [lat], "lon": [lng]})) |
| |
|
| | |
| | if description: |
| | with st.spinner("π§ Generating AI explanation..."): |
| | explanation = explain_prediction_with_llama( |
| | f"In 2-3 sentences, explain why a crime prediction model might classify an incident as '{category}' based on this description: '{description}'. Be concise and factual." |
| | ) |
| | st.subheader("π§ AI Explanation") |
| | st.write(explanation) |
| |
|
| | except Exception as e: |
| | st.error(f"β Prediction Error: {e}") |
| |
|
| | st.markdown("</div>", unsafe_allow_html=True) |
| |
|
| | |
| | st.markdown("---") |
| | st.markdown("<div class='glass-card'>", unsafe_allow_html=True) |
| | st.subheader("π¬ AI Crime Safety Assistant") |
| | st.markdown("Ask me anything about crime prediction, safety tips, or how this system works!", unsafe_allow_html=True) |
| |
|
| | |
| | if 'messages' not in st.session_state: |
| | st.session_state.messages = [ |
| | {"role": "assistant", "content": "π Hello! I'm your AI Crime Safety Assistant. I can help you understand crime patterns, provide safety recommendations, and explain how our prediction model works. What would you like to know?"} |
| | ] |
| |
|
| | |
| | st.markdown("<div class='chat-container'>", unsafe_allow_html=True) |
| | for message in st.session_state.messages: |
| | if message["role"] == "user": |
| | st.markdown(f"<div class='user-message'>π§ {message['content']}</div>", unsafe_allow_html=True) |
| | else: |
| | st.markdown(f"<div class='ai-message'>π€ {message['content']}</div>", unsafe_allow_html=True) |
| | st.markdown("</div>", unsafe_allow_html=True) |
| |
|
| | |
| | col1, col2 = st.columns([5, 1]) |
| | with col1: |
| | user_input = st.text_input("Type your message...", key="chat_input", label_visibility="collapsed", placeholder="Ask about crime safety, predictions, or get recommendations...") |
| | with col2: |
| | send_button = st.button("Send π€", use_container_width=True) |
| |
|
| | |
| | if send_button and user_input: |
| | |
| | st.session_state.messages.append({"role": "user", "content": user_input}) |
| | |
| | |
| | with st.spinner("π§ Thinking..."): |
| | try: |
| | client = get_groq_client() |
| | |
| | |
| | system_prompt = """You are an AI Crime Safety Assistant for a crime prediction system. |
| | You help users understand: |
| | - Crime patterns and trends in San Francisco |
| | - How the XGBoost machine learning model predicts crime categories |
| | - Safety tips and recommendations based on location and time |
| | - What factors influence crime predictions (time, location, historical data) |
| | |
| | Be helpful, concise, and informative. Keep responses to 2-3 sentences unless more detail is needed. |
| | If asked about the model, explain it uses features like latitude, longitude, time, district, and description to predict crime types.""" |
| | |
| | |
| | api_messages = [{"role": "system", "content": system_prompt}] |
| | |
| | |
| | for msg in st.session_state.messages[-5:]: |
| | api_messages.append({"role": msg["role"], "content": msg["content"]}) |
| | |
| | |
| | chat_completion = client.chat.completions.create( |
| | messages=api_messages, |
| | model="llama-3.3-70b-versatile", |
| | temperature=0.7, |
| | max_tokens=500 |
| | ) |
| | |
| | ai_response = chat_completion.choices[0].message.content |
| | |
| | |
| | st.session_state.messages.append({"role": "assistant", "content": ai_response}) |
| | |
| | except Exception as e: |
| | error_msg = f"β οΈ Sorry, I encountered an error: {str(e)}" |
| | st.session_state.messages.append({"role": "assistant", "content": error_msg}) |
| | |
| | |
| | st.rerun() |
| |
|
| | st.markdown("</div>", unsafe_allow_html=True) |
| |
|