| 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) |
|
|