diff --git a/case_files/requirements.txt b/case_files/requirements.txt
deleted file mode 100644
index cdc1fa4..0000000
--- a/case_files/requirements.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-pandas
-geopy
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..e9fc9cc
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,6 @@
+streamlit==1.32.0
+pandas==2.1.0
+folium==0.14.0
+streamlit-folium==0.16.0
+geopy==2.4.0
+numpy==1.24.3
\ No newline at end of file
diff --git a/src/shameer_main.py b/src/shameer_main.py
index f71c320..d51f4e9 100644
--- a/src/shameer_main.py
+++ b/src/shameer_main.py
@@ -1,14 +1,13 @@
-from datetime import datetime
-
+import streamlit as st
import pandas as pd
import json
-import numpy as np
+from datetime import datetime
from geopy.distance import geodesic
+import folium
+from streamlit_folium import folium_static
-# Load data files
def load_data():
- # Load Data
try:
with open('case_files/incident_reports.json', 'r') as f:
incidents = json.load(f)
@@ -19,66 +18,12 @@ def load_data():
with open('case_files/suspects.json', 'r') as f:
suspects = json.load(f)
- # Load CSV Files
bike_logs = pd.read_csv('case_files/bike_logs.csv')
- cam_snapshots = pd.read_csv('case_files/cam_snapshots_metadata.csv')
-
- print("Data successfully loaded")
-
- return incidents, phone_pings, suspects, bike_logs, cam_snapshots
+ return incidents, phone_pings, suspects, bike_logs
except Exception as e:
- print(f"Error loading data: {e}")
- return None
-
-
-def explore_data(incidents, phone_pings, suspects, bike_logs, cam_snapshots):
- # Explore Incidents
-
- print("\nINCIDENT REPORTS:")
- print(f"Number of incidents: {len(incidents)}")
- for i, incident in enumerate(incidents):
- print(f"\nIncident {i + 1}")
- print(f"Date: {incident['date']}")
- print(f"Address: {incident['address']}")
- print(f"Entry Time: {incident['entry_time']}")
- print(f"Exit Time: {incident['exit_time']}")
- print(f"Notes: {incident['notes']}")
-
- # Explore Phone Pings
-
- print("\nPHONE PINGS:")
- print(f"Number of phone pings: {len(phone_pings)}")
- device_ids = set(ping['device_id'] for ping in phone_pings)
- print(f"Unique device ids: {device_ids}")
-
- # Explore Suspects
- print("\nSUSPECTS:")
- print(f"Number of suspects: {len(suspects)}")
- for i, suspect in enumerate(suspects):
- print(f"\nSuspect {i + 1}")
- for key, value in suspect.items():
- print(f"\t{key}: {value}")
-
- print("\nBIKE LOGS:")
- print(f"Total Bike Logs: {len(bike_logs)}")
- print(f"Columns: {list(bike_logs.columns)}")
-
- print("\nCAMERA SNAPSHOTS:")
- print(f"Total Camera Snapshots: {len(cam_snapshots)}")
- print(f"Columns: {list(cam_snapshots.columns)}")
-
- # Connect Device IDS to Suspect
- device_to_suspect = {}
- for suspect in suspects:
- if 'phone_id' in suspect and suspect['phone_id']:
- device_to_suspect[suspect['phone_id']] = suspect['name']
-
- print("\nDEVICE TO SUSPECT MAPPING:")
- for device, name in device_to_suspect.items():
- print(f"\t{device}: {name}")
-
- return device_to_suspect
+ st.error(f"Error loading data: {e}")
+ return None, None, None, None
def parse_timestamps(timestamp_str):
@@ -88,17 +33,14 @@ def parse_timestamps(timestamp_str):
else:
return datetime.strptime(timestamp_str, '%Y-%m-%d')
except Exception as e:
- print(f"Error parsing timestamp {timestamp_str}: {e}")
return None
def analyze_proximity(incidents, phone_pings, device_to_suspect):
- # Start checking which devices were near which incidents
device_at_incidents = {device_id: set() for device_id in device_to_suspect.keys()}
evidence_log = {device_id: [] for device_id in device_to_suspect.keys()}
- # Evidence constraints
- proximity_threshold = 1 # miles
+ proximity_threshold = 1 # miles
time_window = 60 # minutes
for incident in incidents:
@@ -124,12 +66,12 @@ def analyze_proximity(incidents, phone_pings, device_to_suspect):
for ping in phone_pings:
device_id = ping['device_id']
- if device_id not in device_to_suspect: # Device not linked to suspect
+ if device_id not in device_to_suspect:
continue
ping_time = parse_timestamps(ping['timestamp'])
if not ping_time:
- continue # Skip if timestamp couldn't be parsed
+ continue
if time_before <= ping_time <= time_after:
ping_location = (ping['lat'], ping['lon'])
@@ -183,9 +125,8 @@ def analyze_bike_rentals(incidents, bike_logs, suspects):
rental_end = rental['end_time']
for incident in incident_times:
- # Convert both to the same type for comparison
if (rental_start <= incident['exit_time'] and
- rental_end >= incident['entry_time']):
+ rental_end >= incident['entry_time']):
suspect_rentals[suspect_name].add(incident['address'])
return suspect_rentals
@@ -196,96 +137,153 @@ def identify_primary_suspects(device_at_incidents, device_to_suspect, suspect_re
combined_evidence = {}
- # Collect evidence from phone pings
for device_id, addresses in device_at_incidents.items():
suspect_name = device_to_suspect[device_id]
if suspect_name not in combined_evidence:
combined_evidence[suspect_name] = set()
combined_evidence[suspect_name].update(addresses)
- # Add bike rental evidence
for suspect_name, addresses in suspect_rentals.items():
if suspect_name not in combined_evidence:
combined_evidence[suspect_name] = set()
combined_evidence[suspect_name].update(addresses)
- # Calculate a score for each suspect
- suspect_scores = []
+ suspects_scored = []
for suspect_name, addresses in combined_evidence.items():
- # Calculate number of matching addresses
match_count = len(addresses)
- suspect_scores.append((suspect_name, match_count))
-
- # Sort by score (match count) in descending order
- suspect_scores.sort(key=lambda x: x[1], reverse=True)
+ coverage = (match_count / len(all_addresses)) * 100
+ suspects_scored.append({
+ 'name': suspect_name,
+ 'match_count': match_count,
+ 'coverage': coverage,
+ 'addresses': list(addresses)
+ })
- # Get top 3 suspects (or fewer if there aren't 3)
- top_suspects = suspect_scores[:min(3, len(suspect_scores))]
+ suspects_scored.sort(key=lambda x: x['match_count'], reverse=True)
- # Format the results
- results = []
- for suspect_name, match_count in top_suspects:
- phone_evidence = []
- for device_id, addresses in device_at_incidents.items():
- if device_to_suspect[device_id] == suspect_name:
- phone_evidence = list(addresses)
+ return suspects_scored[:3]
- bike_evidence = list(suspect_rentals.get(suspect_name, []))
- justification = f"{suspect_name} was present at {match_count} out of {len(all_addresses)} crime scenes)"
+def create_map(incidents, phone_pings, top_suspect_devices):
+ incident_coords = []
+ for incident in incidents:
+ if "108 Linden St" in incident['address']:
+ incident_coords.append((40.695, -73.92, incident['address'], incident['date']))
+ elif "104 Linden St" in incident['address']:
+ incident_coords.append((40.696, -73.925, incident['address'], incident['date']))
+ elif "102 Linden St" in incident['address']:
+ incident_coords.append((40.697, -73.93, incident['address'], incident['date']))
+
+ center_lat = sum(coord[0] for coord in incident_coords) / len(incident_coords)
+ center_lon = sum(coord[1] for coord in incident_coords) / len(incident_coords)
+
+ m = folium.Map(location=[center_lat, center_lon], zoom_start=16)
+
+ for lat, lon, address, date in incident_coords:
+ folium.Marker(
+ location=[lat, lon],
+ popup=f"{address}
Date: {date}",
+ icon=folium.Icon(color='red', icon='home'),
+ ).add_to(m)
+
+ for ping in phone_pings:
+ if ping['device_id'] in top_suspect_devices:
+ color = 'green'
+ radius = 30
+ fill_opacity = 0.7
+ else:
+ color = 'blue'
+ radius = 10
+ fill_opacity = 0.3
- results.append({
- 'name': suspect_name,
- 'match_count': match_count,
- 'justification': justification,
- 'phone_evidence': phone_evidence,
- 'bike_evidence': bike_evidence
- })
+ folium.CircleMarker(
+ location=[ping['lat'], ping['lon']],
+ radius=radius,
+ popup=f"Device: {ping['device_id']}
Time: {ping['timestamp']}",
+ color=color,
+ fill=True,
+ fill_opacity=fill_opacity
+ ).add_to(m)
- return results
+ return m
-def main():
- # Load Data
- incidents, phone_pings, suspects, bike_logs, cam_snapshots = load_data()
+st.title("Linden Street Burglaries Investigation")
- if incidents is None or phone_pings is None or suspects is None:
- print("Failed to load critical data files. Exiting.")
- return
+if st.button("Analyze Evidence"):
+ incidents, phone_pings, suspects, bike_logs = load_data()
- device_to_suspect = explore_data(incidents, phone_pings, suspects, bike_logs, cam_snapshots)
- device_at_incidents, evidence_log = analyze_proximity(incidents, phone_pings, device_to_suspect)
+ if incidents is None:
+ st.error("Failed to load data files. Check file paths.")
+ st.stop()
- # Debug prints
- print("\nDEVICES AT INCIDENTS:")
- for device_id, addresses in device_at_incidents.items():
- print(f"{device_id}: {addresses}")
+ device_to_suspect = {}
+ for suspect in suspects:
+ if 'phone_id' in suspect and suspect['phone_id']:
+ device_to_suspect[suspect['phone_id']] = suspect['name']
+ device_at_incidents, evidence_log = analyze_proximity(incidents, phone_pings, device_to_suspect)
suspect_rentals = analyze_bike_rentals(incidents, bike_logs, suspects)
+ top_suspects = identify_primary_suspects(device_at_incidents, device_to_suspect, suspect_rentals, incidents)
- # Debug prints
- print("\nSUSPECT RENTALS:")
- for suspect, addresses in suspect_rentals.items():
- print(f"{suspect}: {addresses}")
+ st.header("Top Suspects")
- top_suspects = identify_primary_suspects(device_at_incidents, device_to_suspect, suspect_rentals, incidents)
+ col1, col2, col3 = st.columns(3)
+ columns = [col1, col2, col3]
- # Print the top suspects
- print("\n=== TOP 3 SUSPECTS ===")
for i, suspect in enumerate(top_suspects):
- print(f"\n#{i + 1}: {suspect['name']}")
- print(f"Evidence: {suspect['match_count']} crime scenes")
- print(f"Justification: {suspect['justification']}")
-
- if suspect['phone_evidence']:
- print("Phone evidence at addresses:")
- for address in suspect['phone_evidence']:
- print(f" - {address}")
-
- if suspect['bike_evidence']:
- print("Bike rental evidence at addresses:")
- for address in suspect['bike_evidence']:
- print(f" - {address}")
-
-if __name__ == "__main__":
- main()
\ No newline at end of file
+ with columns[i]:
+ st.subheader(f"#{i + 1}: {suspect['name']}")
+ for s in suspects:
+ if s['name'] == suspect['name']:
+ st.write(f"**Occupation:** {s.get('occupation', 'Unknown')}")
+ st.write(f"**Alibi:** {s.get('alibi', 'None provided')}")
+ st.write(f"**Connections:** {suspect['match_count']} locations ({suspect['coverage']:.0f}%)")
+
+ st.header("Crime Scene Map")
+
+ top_suspect_devices = []
+ for suspect in top_suspects:
+ for s in suspects:
+ if s['name'] == suspect['name'] and 'phone_id' in s:
+ top_suspect_devices.append(s['phone_id'])
+
+ map_figure = create_map(incidents, phone_pings, top_suspect_devices)
+ folium_static(map_figure)
+
+ st.header("Incident Reports")
+ incident_df = pd.DataFrame([{
+ 'Date': incident['date'],
+ 'Address': incident['address'],
+ 'Entry Time': incident['entry_time'],
+ 'Exit Time': incident['exit_time'],
+ 'Notes': incident['notes']
+ } for incident in incidents])
+
+ st.dataframe(incident_df)
+
+ st.header("Evidence Summary")
+
+ tabs = st.tabs(["Phone Evidence", "Bike Rentals"])
+
+ with tabs[0]:
+ for device_id, addresses in device_at_incidents.items():
+ if addresses:
+ st.write(f"**{device_to_suspect[device_id]}'s phone** detected at:")
+ for address in addresses:
+ st.write(f"- {address}")
+
+ with tabs[1]:
+ for suspect_name, addresses in suspect_rentals.items():
+ if addresses:
+ st.write(f"**{suspect_name}** rented bikes during crimes at:")
+ for address in addresses:
+ st.write(f"- {address}")
+
+ primary_suspect = top_suspects[0]['name'] if top_suspects else "No definitive suspect"
+ st.success(f"Primary suspect: **{primary_suspect}**")
+
+else:
+ st.info("Click 'Analyze Evidence' to view results")
+
+# Run 'streamlit run src/shameer_main.py' in terminal
\ No newline at end of file