A high-performance, fully automated location-risk analytics engine and modular UI tab built for seamless cross-platform integration. SafeZone transforms disparate, raw municipal crime datasets into standardized, street-level safety indexes ("BlockScores"), allowing real-time risk evaluation within any map-based application.
Built natively in React Native (Expo) and decoupled via an API-first backend architecture, this repository provides a turn-key solution for platforms looking to increase user retention and transaction security by embedding hyper-local safety insights.
- Real Estate Platforms: Embed dynamic neighborhood-level safety ratings directly onto property listing layouts.
- Travel & Hospitality Tech: Power safe-route navigation matrices and accommodation risk screening.
- Corporate Duty of Care: Integrate automated travel-risk notifications and localized geofencing for traveling workforces.
- Weighted Risk Engine: Rather than dumping raw police blotters on a map, the engine runs a data normalization formula that weights incidents by severity (e.g., separating low-impact property incidents from personal safety threats) to generate a highly accurate, local safety metric.
- Low-Cost Automated Scraping: Scheduled cron functions automatically fetch, sanitize, and index municipal records without requiring manual oversight.
- Decoupled Architecture: The
SafeZonemap tab is fully encapsulated. It can be copy-pasted into any existing React Native application with zero structural refactoring. - State Optimization: Leverages optimized regional caching to prevent redundant API map fetches when a user pans across city grids.
- Single-codebase architecture optimized simultaneously for Web (Vercel/Netlify), iOS (Apple App Store), and Android (Google Play Store) utilizing Expo Application Services (EAS).
├── .github/ # CI/CD workflows
├── apps/
│ ├── web/ # Next.js App Router frontend (Vercel)
│ └── api/ # Express + Prisma backend (Railway)
├── packages/
│ ├── crime-data/ # 37-city open-data adapters + safety-score
│ └── db/ # Prisma schema + client
├── workers/
│ └── boston-proxy/ # Cloudflare-style CKAN proxy
└── docs/ # Architecture, methodology, audits
- Node.js v22 (see
package.jsonengines) - npm 9+ with workspace support
- PostgreSQL (Neon free tier works) + Redis (Upstash free tier works)
-
Clone the repository:
git clone https://github.com/damienmcdade/TravelSafe.git cd CommunitySafe -
Configure environment variables: Copy
.env.exampleto.env.localat the repo root and fill in theDATABASE_URL,REDIS_URL,JWT_SECRET, and AI provider keys. Bothapps/webandapps/apiread the root.env.localby default. -
Install + generate Prisma client + build packages:
npm install npm run db:generate npm run build --workspace packages/crime-data
-
Run both apps in parallel:
npm run dev # api on :8080, web on :3000
| Surface | Platform | Trigger |
|---|---|---|
apps/web |
Vercel | git push to main (auto) |
apps/api |
Railway | git push to main (auto) |
| Database | Neon | managed |
| Redis | Railway plugin | managed |
The legacy build commands below are kept only for reference — the live
deploy path is git push origin main.
# Compile and build iOS Production Target
eas build --platform ios --profile production
# Compile and build Android Production Target (.aab bundle)
eas build --platform android --profile production
# Export Static optimized Web single-page deployment bundle
npx expo export --platform webThis codebase was intentionally designed and architected to serve as a drop-in acquisition asset for larger consumer applications. All logic layers—from the spatial database queries to the interface renderings—are fully decoupled and thoroughly documented.
For inquiries regarding complete asset acquisition, proprietary algorithm licensing, or to request access to our structural core architecture breakdown, please contact:
- Principal Developer: [Damien Gantt-McDade]
- Professional Contact: [damienmcdade17@gmail.com]
- Digital Portfolio/LinkedIn: [www.linkedin.com/in/damien-gantt-mcdade-62a30629a]
These are non-negotiable. They are enforced in code (Prisma schema, post pre-vetter, registry link-out component) and in review.
- No demographic data is ever collected — no ethnicity, gender, sexual orientation, religion, or age fields exist in the schema. Do not add them.
- Never display, track, or geolocate individual named people as threats. No threat card may carry a person's name, photo, or attributed crime. Threat awareness is area-level only (neighborhood / SDPD beat).
- Sex-offender info is a link-out to the official public registry (Megan's Law in California by default). We do not re-host or re-display individuals' data in-app.
- Community posts default to area-level. The pre-vetter blocks or holds for manual review any post that names a specific street address or person. Submission UI shows a defamation / accuracy warning before posting.
- Data provenance is always shown — every screen that displays crime data surfaces source name, dataset recency, and the note that coverage is neighborhood-level (not live street-level).
- Personal Safety features (check-in, live share) are user-initiated and use only the user's own location, shared only with their own trusted contacts. They are not used to populate any community-facing surface.
- The app does not dispatch emergency services. There is no in-app SOS.
The Personal Safety tab leads with guidance to dial 911 directly via a
device-native
tel:action that must work even if the app backend is down. - Trusted contacts must opt in. A confirmation link is emailed to each
newly-added contact; only
CONFIRMEDcontacts receive any notification. - Reliability honesty. Status surfaces show real delivery state, never optimistic assumptions; if the API is unreachable at timer-arm time, the timer is refused with a visible error rather than armed in a state the backend can't honor.
CommunitySafe/ # npm workspaces monorepo
├── apps/
│ ├── web/ # Next.js (App Router) + Tailwind → deploys to Vercel
│ └── api/ # Express + JWT + Prisma client → deploys to Railway
└── packages/
└── db/ # Shared Prisma schema + seed → Railway Postgres
The Next.js app calls the Express API over HTTPS using NEXT_PUBLIC_API_BASE_URL.
Web Push notifications are signed with VAPID keys held by the API and delivered
via the service worker in apps/web/public/sw.js.
| Adapter | Source | Endpoint | Granularity | Use |
|---|---|---|---|---|
sandag-socrata (primary) |
SANDAG Crime Data on San Diego County Open Data Portal | https://data.sandiegocounty.gov/resource/486f-q228.json |
Yearly rates by jurisdiction | getAreaStats |
sdpd-nibrs (secondary) |
City of San Diego Police NIBRS Crime Offenses | https://seshat.datasd.org/police_nibrs/pd_nibrs_<year>_datasd.csv |
Incident-level, aggregated to neighborhood/beat, quarterly | getIncidents, getRecentReports |
mock (fallback) |
In-repo sample data, clearly labeled | n/a | Sample | Local dev, rate-limit / outage fallback |
SDPD/SANDAG data is aggregated to neighborhood/beat level and refreshed weekly to quarterly — not in real time. The UI must continue to state this clearly.
- Node.js 20+ (Railway and Vercel both default to current LTS)
- A PostgreSQL database (Docker one-liner below works fine)
npm10+
git clone git@github.com:damienmcdade/TravelSafe.git
cd CommunitySafe
cp .env.example .env # fill in values
npm install
docker run --name travelsafe-pg -e POSTGRES_PASSWORD=postgres -p 5432:5432 -d postgres:16
npm run db:generate
npm run db:migrate -- --name init
npm run db:seednpm run dev # starts apps/web (3000) and apps/api (4000) in parallelOther useful scripts:
npm run db:studio # Prisma Studio
npm run typecheck # type-check every workspace
npm run lint # lint every workspacevercel linkfrom the repo root (the includedvercel.jsonpoints atapps/webas the project root).- In the Vercel dashboard, set environment variables from
.env.example. The web app only needs theNEXT_PUBLIC_*ones at build time. vercel --prod(or push tomainif Git integration is enabled).
- Create a new Railway project and add the Postgres plugin — it sets
DATABASE_URLfor you. - Add a service from this repo. The included
railway.jsonbuilds withnpm install && db:generate && build:api, then at boot runsdb:push && start:apiwith/healthas the healthcheck path.db:pushsyncs the Prisma schema to Postgres directly (no migration files required) — suitable for a project pre-launch. Once you have production data to preserve, replacedb:pushwithdb:migrate:deployinrailway.jsonand check committed migration files intopackages/db/prisma/migrations/. - Set the remaining env vars (
JWT_SECRET,VAPID_*,CORS_ORIGINS— include your Vercel URL). - Copy the public Railway URL of the API service into Vercel as
NEXT_PUBLIC_API_BASE_URLand redeploy the web app.
git add .
git commit -m "Initial CommunitySafe scaffold"
git push -u origin mainThe GitHub remote origin was set when the repo was cloned —
git remote -v should show damienmcdade/TravelSafe.
See .env.example for the full annotated list. Highlights:
| Variable | Required by | Notes |
|---|---|---|
DATABASE_URL |
api, db | Provided automatically by Railway Postgres |
JWT_SECRET |
api | Generate with openssl rand -base64 48 |
VAPID_PUBLIC_KEY / VAPID_PRIVATE_KEY |
api | Generate with npx web-push generate-vapid-keys |
NEXT_PUBLIC_VAPID_PUBLIC_KEY |
web | Mirror of the public key, exposed to the browser |
NEXT_PUBLIC_API_BASE_URL |
web | Vercel needs this set to the Railway API URL |
SANDAG_SOCRATA_APP_TOKEN |
api | Optional; raises Socrata rate limit |
CRIME_DATA_ADAPTER |
api | auto (default), sandag, sdpd, or mock |
| Tab / Screen | Path | Backend route |
|---|---|---|
| Welcome (no sign-up required) | / |
— links straight to /threats; auth is optional |
| Register / login | /register, /login |
POST /auth/register, POST /auth/login — only needed for posting / trusted contacts / check-in timer |
| Awareness (citywide by default) | /threats |
GET /crime-data/citywide, GET /crime-data/insights?…, location search via GET /geo/lookup, geo opt-in |
| Crime Map | /map |
Leaflet + OpenStreetMap, circle markers per neighborhood sized by incident volume + calm color bands |
| Official alerts sidebar | (on CommunitySafe + Neighborhood Watch) | GET /official-alerts — currently National Weather Service; SDPD press releases and CHP TODO |
| Live community pulse | (on CommunitySafe) | GET /community/stream (SSE) — new VERIFIED posts insert in real time |
| AI compose coach | (in CommunitySafe composer) | POST /ai/compose-feedback streams from anthropic/claude-haiku-4-5 via Vercel AI Gateway; falls back silently when AI_GATEWAY_API_KEY is unset |
| Onboarding — alert categories | /onboarding/alert-preferences |
PUT /preferences/alerts |
| Onboarding — trusted contacts | /onboarding/trusted-contacts |
POST /contacts, GET /contacts (max 5) |
| Threat Detection | /threats |
GET /crime-data/alerts?neighborhood=… + push when entering higher-incident area |
| Personal Safety | /safety |
Emergency-call guidance + check-in timer + live-share. No in-app SOS / alert dispatch. See below. |
| ↳ 911 dial | client-side tel:911 link — never depends on backend |
|
| ↳ Check on me | POST /safety/check-in, POST /safety/check-in/:id/safe (server-side worker fires expiries) |
|
| ↳ Live share | POST /safety/live-share, GET /share/:token (web), DELETE /safety/live-share/:id |
|
| ↳ Safe route | POST /safety/safe-route (area-risk flagged, area-level only) |
|
| Trusted contact opt-in | /contacts/confirm/:token |
POST /contacts/:id/confirm, POST /contacts/:id/resend |
| CommunitySafe — City Scanner | /community |
GET /crime-data/area-stats?jurisdiction=…, GET /community/posts |
| Official registry link-out | /community (panel) |
static link, never re-displays individuals |
| Submit warning | /community (modal) |
POST /community/posts → pre-vetter → verification queue |
| Report / block / mute | /community |
POST /moderation/reports, POST /moderation/block, POST /moderation/mute |
| Neighborhood Watch | /neighborhood |
GET /neighborhood/feed?neighborhood=… |
- Submission UI requires the user to acknowledge a defamation / accuracy warning.
- The pre-vetter (
services/moderation/post-prevet.ts) inspects the draft:- posts containing a likely street address (
/\d+\s+\w+\s+(st|ave|blvd|…)/i) or a likely personal name are held for manual review, not auto-published; - profanity-filtered terms are flagged;
- rate-limit: max N posts / user / hour.
- posts containing a likely street address (
- Posts that pass the pre-vetter enter the queue as
PENDING. A moderator moves them toVERIFIEDorREJECTED. OnlyVERIFIEDposts surface in the community feed by default;PENDINGposts are visible only to the author.
Source code: All Rights Reserved (proprietary). Public crime-data
adapters wrap third-party open-data sources; their terms are described
in THIRD_PARTY_NOTICES.md. No license is granted to use, copy,
modify, or distribute this code without explicit written permission
from the project owner.
For inquiries: open an issue at https://github.com/damienmcdade/TravelSafe/issues