Skip to content

Tanmay244/safewalk-nyc

Repository files navigation

SafeWalk NYC πŸ—½

AI-powered pedestrian safety routing for New York City β€” built for students walking at night.

SafeWalk NYC combines real-time crime data, streetlight complaints, traffic signal density, and live news alerts to score walking routes 0–100. Claude AI explains the recommendation in plain English so users make informed decisions, not just fast ones.


Demo

User: "Walk me from 3009 Broadway to Times Square"

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  3 ROUTES FOUND              RECOMMENDED: Route 1               β”‚
β”‚                                                                 β”‚
β”‚  Route 1  β˜… Best    98/100 Safe    via Broadway      3.97 mi    β”‚
β”‚  Route 2            98/100 Safe    via Central Park  4.74 mi    β”‚
β”‚  Route 3            97/100 Safe    via Riverside     4.43 mi    β”‚
β”‚                                                                 β”‚
β”‚  "Route 1 via Broadway is your best option β€” zero crime         β”‚
β”‚   incidents, shortest at 3.97 mi, well-traveled corridor."     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

All 3 routes appear on the Mapbox map simultaneously, each color-coded by safety score with floating score badges.


Features

  • Conversational input β€” describe your destination naturally; Claude parses intent into street addresses
  • 3 route alternatives β€” direct path + two corridor variants (east/west) scored independently
  • Live safety data β€” NYPD crime reports (90-day window), NYC 311 lighting complaints, traffic signal density
  • Real-time news β€” Linkup API surfaces construction closures and safety alerts near your route
  • Score badges on map β€” all 3 routes visible simultaneously with floating score chips
  • Per-route cards β€” each card shows its distinguishing corridor, Crime/Lighting/Signal bars, and an expandable explanation
  • Time-aware scoring β€” same street scores lower at 2 AM (Γ—2.0) than at noon (Γ—1.0)
  • Campus safety alerts β€” "I feel unsafe" button routes to Columbia, NYU, Barnard, or 911

Architecture

Full Request Pipeline

User message  (e.g. "Walk me from 3009 Broadway to Times Square")
     β”‚
     β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Step 1 β€” Claude: Parse Intent       β”‚
β”‚                                      β”‚
β”‚  Extracts origin + destination as    β”‚
β”‚  precise street addresses.           β”‚
β”‚  "Columbia Biz School" β†’ "665 W 130th St"
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                    β”‚
                    β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Step 2 β€” Geocoding (parallel)       β”‚
β”‚                                      β”‚
β”‚  Mapbox Geocoding v5                 β”‚
β”‚  β€’ types: address, poi, neighborhood β”‚
β”‚  β€’ bbox: NYC boroughs only           β”‚
β”‚  β€’ NYC coordinate validation         β”‚
β”‚                                      β”‚
β”‚  Nominatim (OSM) fallback            β”‚
β”‚  β€’ handles institutional POIs        β”‚
β”‚  β€’ viewbox: NYC, bounded=1           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                    β”‚  origin + dest coordinates
                    β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Step 3 β€” Mapbox Directions  (3 parallel requests)       β”‚
β”‚                                                          β”‚
β”‚  Request A:  direct route          (no waypoint)         β”‚
β”‚  Request B:  eastern variant       (mid + perp Γ— 0.012Β°) β”‚
β”‚  Request C:  western variant       (mid βˆ’ perp Γ— 0.012Β°) β”‚
β”‚                                                          β”‚
β”‚  Deduplicate by 2nd-most-prominent street + distance     β”‚
β”‚  β†’ up to 3 distinct MapboxRoute objects                  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           β”‚
                           β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Step 4 β€” Segment Splitting  (~200 m chunks)             β”‚
β”‚                                                          β”‚
β”‚  Each Mapbox step β†’ RouteSegment { start, end, geom }    β”‚
β”‚  Shared endpoint deduplication                           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           β”‚
                           β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Step 5 β€” Data Enrichment  (per segment, all parallel)   β”‚
β”‚                                                          β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚   β”‚  NYPD Crimes    β”‚  β”‚  311 Lighting   β”‚  β”‚ Traffic β”‚ β”‚
β”‚   β”‚  250 m radius   β”‚  β”‚  150 m radius   β”‚  β”‚ Signals β”‚ β”‚
β”‚   β”‚  90 days back   β”‚  β”‚  open only      β”‚  β”‚ 150 m   β”‚ β”‚
β”‚   β”‚  5uac-w243      β”‚  β”‚  erm2-nwe9      β”‚  β”‚ e5mv-wy8β”‚ β”‚
β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚                                                          β”‚
β”‚   + Linkup news (once per route, at route midpoint)      β”‚
β”‚     β†’ construction closures, crime alerts, detours       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           β”‚
                           β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Step 6 β€” Safety Scoring                                 β”‚
β”‚                                                          β”‚
β”‚  Per segment:                                            β”‚
β”‚    crimePenalty    = Ξ£(crime Γ— severity) [FELONY=10]    β”‚
β”‚    lightPenalty    = openComplaints Γ— 8  [cap 40]       β”‚
β”‚    signalPenalty   = 0/5/15 for β‰₯2/1/0 signals          β”‚
β”‚    newsPenalty     = Ξ£ severity         [cap 15]        β”‚
β”‚                                                          β”‚
β”‚    raw    = crimeΓ—0.35 + lightΓ—0.25                      β”‚
β”‚             + signalΓ—0.15 + newsΓ—0.10                    β”‚
β”‚    score  = 100 βˆ’ raw Γ— timeModifier                     β”‚
β”‚                                                          β”‚
β”‚  timeModifier:  06–20h Γ—1.0 Β· 20–24h Γ—1.5               β”‚
β”‚                 00–04h Γ—2.0 Β· 04–06h Γ—1.3               β”‚
β”‚                                                          β”‚
β”‚  Route score = distance-weighted avg of segments         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           β”‚
                           β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Step 7 β€” Claude: Explanation                            β”‚
β”‚                                                          β”‚
β”‚  Summarises all 3 routes, recommends the best by name,  β”‚
β”‚  cites crime counts, lighting issues, worst block.       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           β”‚
                           β–Ό
                    API Response
          { routes[], recommended, explanation }

Safety Score Breakdown

                  Crime (35%)        Lighting (25%)
                      β”‚                   β”‚
        NYPD data ─────        311 data ───
        90 days       β”‚        open only  β”‚
        250 m         β”‚        150 m      β”‚
                      β–Ό                   β–Ό
                 crimePenalty        lightPenalty
                      β”‚                   β”‚
                      β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                               β”‚
              Signal (15%) ─────◄── Traffic signals 150 m
              News   (10%) ─────◄── Linkup alerts/closures
                               β”‚
                               β–Ό
                    raw = weighted sum
                               β”‚
                               β–Ό
                   score = 100 βˆ’ raw Γ— T
                               β”‚
               β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
               β–Ό               β–Ό               β–Ό
           T = 1.0         T = 1.5         T = 2.0
          (6 AM–8 PM)    (8 PM–midnight)  (midnight–4 AM)

Score labels:  β‰₯ 80  🟒 Safe
               β‰₯ 60  🟑 Moderate
               β‰₯ 40  🟠 Use Caution
                < 40  πŸ”΄ High Risk

Frontend Data Flow

route-planner/page.tsx
        β”‚
        β”‚  User types destination
        β–Ό
ChatInput.tsx ──POST /api/chat──► Claude (conversational)
        β”‚                               β”‚
        β”‚  onRouteExtracted(org, dst)   β”‚ plain-text reply
        β–Ό                               β–Ό
handleRouteExtracted()          Chat bubble in sidebar
        β”‚
        β”‚  POST /api/score-route
        β–Ό
ApiResponse { routes[], recommended, explanation }
        β”‚
        β”œβ”€β”€β–Ί MapView.tsx  (Mapbox GL JS, SSR: false)
        β”‚     β€’ All 3 polylines drawn simultaneously
        β”‚     β€’ Color by score: green / yellow / orange / red
        β”‚     β€’ Selected route: opacity 1.0, width 6 px
        β”‚     β€’ Others: opacity 0.45, width 3 px
        β”‚     β€’ Score badge (pill) at each route midpoint
        β”‚     β€’ Origin (blue dot) + Destination (red dot)
        β”‚     β€’ Camera fits all routes on load
        β”‚
        └──► RouteCard.tsx  Γ—3  (one per route)
              β€’ "Route N  Β·  via [street]"
              β€’ β˜… Best badge on recommended
              β€’ Safety score + Shield icon
              β€’ Duration Β· Distance Β· Incidents
              β€’ Crime / Lighting / Signals progress bars
              β€’ Expandable explanation
                - Recommended β†’ full Claude narrative
                - Others      β†’ auto-generated from data

File Structure

safewalk-nyc/
β”œβ”€β”€ app/
β”‚   β”œβ”€β”€ page.tsx                    Landing page with email signup
β”‚   β”œβ”€β”€ layout.tsx                  Root layout β€” loads globals.css
β”‚   β”œβ”€β”€ globals.css                 Mapbox CSS + Tailwind directives
β”‚   β”œβ”€β”€ route-planner/
β”‚   β”‚   └── page.tsx                Main app: map + chat sidebar
β”‚   └── api/
β”‚       β”œβ”€β”€ score-route/route.ts    POST β€” full 7-step scoring pipeline
β”‚       β”œβ”€β”€ chat/route.ts           POST β€” conversational assistant
β”‚       └── alert/route.ts          POST β€” campus safety alert routing
β”‚
β”œβ”€β”€ components/
β”‚   β”œβ”€β”€ MapView.tsx                 Mapbox GL map (dynamic import, SSR false)
β”‚   β”œβ”€β”€ RouteCard.tsx               Per-route card with safety bars
β”‚   β”œβ”€β”€ ChatInput.tsx               Conversational input + message history
β”‚   β”œβ”€β”€ AlertButton.tsx             Persistent "I feel unsafe" button
β”‚   └── SignupForm.tsx              Email signup with university detection
β”‚
β”œβ”€β”€ lib/
β”‚   β”œβ”€β”€ types.ts                    All shared TypeScript interfaces
β”‚   β”œβ”€β”€ scoring.ts                  scoreSegment() Β· scoreRoute() Β· scoreLabel()
β”‚   β”œβ”€β”€ alert-routing.ts            Email domain β†’ campus safety contacts
β”‚   └── api/
β”‚       β”œβ”€β”€ nyc-open-data.ts        NYPD Β· 311 Β· traffic signal fetchers
β”‚       β”œβ”€β”€ linkup.ts               Linkup search + news penalty calculator
β”‚       └── claude.ts               Anthropic SDK wrappers
β”‚
β”œβ”€β”€ .env.local.example              Required environment variables template
β”œβ”€β”€ CLAUDE.md                       Project conventions (AI-assisted coding)
└── README.md

Tech Stack

Layer Technology
Framework Next.js 14 β€” App Router, TypeScript
Styling Tailwind CSS
Map Mapbox GL JS (dynamic import, SSR disabled)
AI Anthropic Claude claude-sonnet-4-6
Crime data NYC Open Data β€” NYPD Complaints 5uac-w243
Lighting data NYC Open Data β€” 311 Service Requests erm2-nwe9
Signal data NYC Open Data β€” Traffic Signals e5mv-wy8f
News alerts Linkup API
Geocoding Mapbox Geocoding v5 + Nominatim (OSM) fallback
Routing Mapbox Directions v5 β€” 3 variants via forced waypoints

Getting Started

1. Clone and install

git clone https://github.com/Tanmay244/safewalk-nyc.git
cd safewalk-nyc
npm install

2. Configure environment variables

cp .env.local.example .env.local

Edit .env.local:

ANTHROPIC_API_KEY=sk-ant-...          # console.anthropic.com
LINKUP_API_KEY=...                    # linkup.so
NEXT_PUBLIC_MAPBOX_TOKEN=pk.eyJ...    # mapbox.com

3. Run dev server

npm run dev
# β†’ http://localhost:3000

Navigate to /route-planner and try:

Walk me from 3009 Broadway to Times Square

You should see 3 routes on the map with safety score badges.


API Reference

POST /api/score-route

Score 3 walking route alternatives between two NYC addresses.

Request body

{
  "message": "Walk me from 3009 Broadway to Times Square",
  "userLocation": { "lat": 40.8095, "lng": -73.9634 }
}

Response

{
  "routes": [
    {
      "path": { "type": "LineString", "coordinates": [[...]] },
      "score": 98,
      "scoreLabel": { "label": "Safe", "color": "green" },
      "distanceMiles": 3.97,
      "durationMinutes": 74,
      "label": "via Broadway",
      "segments": [
        {
          "streetName": "Broadway",
          "score": 98,
          "crimePenalty": 0,
          "lightingPenalty": 4,
          "trafficSignalPenalty": 0,
          "crimeCount": 0,
          "openLightingIssues": 1,
          "nearbySignals": 3
        }
      ]
    }
  ],
  "recommended": 0,
  "explanation": "Route 1 via Broadway is your best option...",
  "origin": { "lat": 40.8095, "lng": -73.9634 },
  "destination": { "lat": 40.7580, "lng": -73.9855 },
  "timeOfDay": 13
}

POST /api/chat

Conversational assistant for safety questions and route queries.

Request

{
  "messages": [{ "role": "user", "content": "Is it safe to walk at midnight?" }],
  "context": { "currentLocation": "Columbia University" }
}

Response

{
  "reply": "Walking late at night has some risks worth knowing...",
  "route": { "origin": "Columbia University", "destination": "Penn Station" }
}

POST /api/alert

Route a safety alert to campus security or emergency contacts.

Request

{
  "email": "student@columbia.edu",
  "location": { "lat": 40.8075, "lng": -73.9626 },
  "message": "I feel unsafe near 120th and Amsterdam"
}

University Safety Contacts

Email domain detection automatically routes alerts to the right team.

University Domain Emergency
Columbia University columbia.edu 212-854-5555
NYU nyu.edu 212-998-2222
Barnard College barnard.edu Barnard Security
The New School newschool.edu Campus Safety
Fordham University fordham.edu Fordham Security

Adding a university β€” append to UNIVERSITIES in lib/alert-routing.ts:

{ name: "My University", domain: "myuni.edu", safetyEmail: "safety@myuni.edu", color: "#3b82f6" }

Score Reference

Score Label Meaning
80–100 🟒 Safe Well-lit, low crime, good pedestrian signals
60–79 🟑 Moderate Some concerns; take normal precautions
40–59 🟠 Use Caution Elevated risk; stay aware of surroundings
0–39 πŸ”΄ High Risk Avoid if possible, especially at night

Scores are time-adjusted β€” the same block is scored lower after dark:

06:00 – 20:00  Γ—1.0  (baseline)
20:00 – 24:00  Γ—1.5  (evening penalty)
00:00 – 04:00  Γ—2.0  (night penalty)
04:00 – 06:00  Γ—1.3  (pre-dawn penalty)

Roadmap

  • Real-time MTA transit alerts β†’ route avoidance
  • Email delivery for safety alerts (Resend / SendGrid)
  • Persist signups to Supabase
  • Rate limiting via @upstash/ratelimit
  • User accounts + saved routes
  • Mobile app (React Native / Expo)

Contributing

Pull requests welcome. For major changes, open an issue first.


License

MIT β€” built for NYC students who deserve to feel safe getting home.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages