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