When there's no data connection, Bachao encodes your emergency type and location into a tiny SMS and hands it to your phone's messaging app. Watch it in action:
βΆ Play the full-quality video (
offline-demo.mp4) for sound and higher resolution.
Emergencies don't wait for good signal. Bachao is built around a simple idea: a single SMS should be enough to summon help. It combines three tightly-linked channels so a citizen in distress always has a path to a responder.
| Channel | What it does |
|---|---|
| Mobile app | Registration, live map-based SOS, and an offline SMS fallback |
| Backend | Users, guardians, incidents, dispatch, dashboards, real-time WebSockets |
| SMS protocol | Packs emergency type + geohashed location into one short message |
- Citizen registration with phone verification, profile details, and identity photo uploads
- JWT login using phone number plus OTP
- Guardian registration with certificate upload and admin approval
- Guardian availability, location updates, and incident-response tracking
- App-created SOS incidents with live location
- SMS-created incidents from compact messages such as
SMS-M-dr5ru7b2n0-X9K2Q1 - Nearest-guardian lookup using Haversine distance
- Dispatcher/admin incident visibility and manual guardian assignment
- HTML backend dashboards for overview, users, incoming reports, and assignments
- WebSocket broadcasts for incident creation and guardian pings
- Full middleware stack: request IDs, timing, logging, rate limiting, validation, compression, error handling, and metrics
ββββββββββββββββββββββββββββ
Online SOS βββΆ β β
β FastAPI Backend ββββΆ Nearest Guardians
Offline SMS ββΆ β users Β· incidents Β· ββββΆ WebSocket broadcasts
(via gateway) β guardians Β· dispatch ββββΆ Admin dashboards
β β
βββββββββββββ¬βββββββββββββββ
β
π SQLite (bachao.db)
- Online path: app β
POST /api/sos/β incident created β nearby guardians found β real-time broadcast. - Offline path: app encodes
SMS-{TYPE}-{GEOHASH}-{ID}β SMS gateway βPOST /api/sms/ingressβ incident created β real-time broadcast.
D:\bachao
βββ backend/
β βββ app/main.py # FastAPI app, middleware, routers, startup DB init
β βββ api/ # REST and HTML dashboard routes
β βββ core/ # config, auth, database session, geohash, DB init
β βββ middleware/ # request/response middleware stack
β βββ model/ # Pydantic schemas and SQLAlchemy models
β βββ services/ # user, guardian, incident, dispatcher, SMS decoder logic
β βββ template/ # Jinja2 dashboard templates
β βββ ws/ # WebSocket endpoints
β βββ run_server.py # local backend runner
β βββ assign_admin.py # role/admin management helper for SQLite users
β βββ requirements.txt # Python dependencies
β βββ Dockerfile # backend container image
βββ frontend/
β βββ app/
β βββ apps/expo-app/ # Expo Router mobile application
β βββ packages/ # workspace packages: UI, Supabase, auth/account
β βββ tooling/ # shared ESLint, Prettier, Tailwind, TS configs
β βββ package.json # frontend monorepo scripts
β βββ pnpm-workspace.yaml
β βββ turbo.json # Turborepo task config
βββ packages/
β βββ sms-core/ # shared SMS protocol docs and encode/decode utilities
βββ docker-compose.yml # backend + (stale) frontend-dev service reference
βββ package.json # root convenience scripts (partially stale)
βββ README.md
Generated/runtime directories present in this checkout (
node_modules,backend/venv,backend/uploads,.expo,.turbo, caches,bachao.db) should normally be git-ignored.
|
Backend
|
Frontend
|
Shared SMS package
|
cd backend
python -m venv venv
venv\Scripts\activate # Windows
python -m pip install -r requirements.txt
python run_server.pyBackend runs at http://localhost:8000
| URL | Purpose |
|---|---|
http://localhost:8000/ |
Root |
http://localhost:8000/docs |
Interactive API docs |
http://localhost:8000/api/health |
Health check |
http://localhost:8000/api/backend-dashboard |
Admin dashboard |
http://localhost:8000/api/backend-dashboard/incoming-reports |
Incoming reports |
The active Expo workspace lives under frontend/app (not directly under frontend).
cd frontend/app
pnpm install
cd apps/expo-app
pnpm startOr run through the monorepo:
cd frontend/app
pnpm devUseful Expo commands: pnpm android, pnpm ios, pnpm web, pnpm typecheck, pnpm lint.
The root
frontend:devscript runscd frontend && npx expo start, but the active app is underfrontend/app/apps/expo-app. Prefer the commands above until the root scripts are updated.
API_PREFIX=/api
DEBUG=true
SECRET_KEY=replace-this-in-development-too
ALGORITHM=HS256
ACCESS_TOKEN_EXPIRE_MINUTES=30
DATABASE_URL=sqlite:///./bachao.db
DATABASE_URLis defined in settings, but the active SQLAlchemy engine currently uses the hardcoded value inbackend/core/database.py. ChangingDATABASE_URLalone won't move the database.
EXPO_PUBLIC_API_URL=http://localhost:8000
EXPO_PUBLIC_API_PREFIX=/api
EXPO_PUBLIC_WS_URL=ws://localhost:8000
EXPO_PUBLIC_SMS_FORWARDING_RECEIVER="+9779863514215"
EXPO_PUBLIC_SUPABASE_API_URL=
EXPO_PUBLIC_SUPABASE_ANON_KEY=| Target | API / WS URL |
|---|---|
| Android emulator | http://10.0.2.2:8000 / ws://10.0.2.2:8000 |
| Physical device | http://192.168.x.x:8000 / ws://192.168.x.x:8000 (LAN IP) |
Do not commit real secrets or production tokens.
The emergency SMS format packs everything into one short string:
SMS-{TYPE}-{GEOHASH}-{ID}
The shared package spec also documents a legacy SOS-{TYPE}-{GEOHASH}-{ID} form. The backend local decoder accepts both SMS and SOS; the current mobile app uses SMS.
| Code | Emergency Type |
|---|---|
M |
π Medical |
P |
π Police |
F |
π₯ Fire |
G |
π General |
N |
πͺοΈ Natural Disaster |
A |
π₯ Accident |
Example
SMS-M-tudr4et3f9-X9K2Q1
app/utils/smsEncoder.ts encodes the emergency type and current/cached coordinates into a 10-character geohash. The backend decoder turns that geohash back into coordinates before creating the incident.
All REST routes are mounted under /api.
Health
GET /api/health β {"status": "ok"}
Users & Authentication
POST /api/users/register/step1 # JSON phone verification (dev OTP: 123456)
POST /api/users/register/step2 # form: session_id, name, age, email, nid?
POST /api/users/register/step3 # multipart photos β creates user, returns token
POST /api/users/login # form: phone_number, otp (dev OTP: 1234)
GET /api/users/profile # Authorization: Bearer <token>
Registration is a three-step flow; authenticated routes use Authorization: Bearer <token>.
Guardians
POST /api/guardians/register # multipart certificate_photo (starts pending)
GET /api/guardians/profile
POST /api/guardians/respond/{incident_id} # ACCEPTED | DECLINED | ARRIVED | COMPLETED
POST /api/guardians/location
POST /api/guardians/availability
GET /api/guardians/pending # admin only
POST /api/guardians/approve/{guardian_id} # admin only
When a guardian accepts an incident, it is assigned to that guardian and moved to IN_PROGRESS.
SOS
POST /api/sos/
{ "type": "Medical", "lat": 27.7172, "lng": 85.3240, "device_id": "device-id" }Creates an APP incident β finds nearby available guardians β broadcasts INCIDENT_CREATED and GUARDIANS_PINGED.
SMS Ingress
POST /api/sms/ingress
{ "sender": "+9779812345678", "body": "SMS-M-tudr4et3f9-X9K2Q1" }Decodes the body β maps code to incident type β decodes geohash to lat/lng β links to a registered user if the sender exists β creates an SMS incident β broadcasts events.
Dispatcher
GET /api/dispatch/incidents
GET /api/dispatch/incidents/active
Visibility by role β ADMIN/DISPATCHER: all Β· GUARDIAN: active Β· USER: own incidents only.
Backend Dashboard
GET /api/backend-dashboard
GET /api/backend-dashboard/stats
GET /api/backend-dashboard/incidents
GET /api/backend-dashboard/users
GET /api/backend-dashboard/guardians
GET /api/backend-dashboard/incoming-reports
POST /api/backend-dashboard/assign-guardian
POST /api/backend-dashboard/unassign-guardian
GET /api/backend-dashboard/incident/{incident_id}
These routes do not enforce authentication in code. Put them behind auth or network controls before production.
Metrics
GET /api/metrics
GET /api/metrics/summary
GET /api/metrics/endpoint/{endpoint}
Generated by APIMetricsMiddleware and stored in memory for the current process.
Mounted outside /api:
ws://localhost:8000/ws/incidents
ws://localhost:8000/ws/guardians
{
"event": "INCIDENT_CREATED",
"incident": {
"id": 1, "external_id": "uuid", "type": "Medical",
"latitude": 27.7172, "longitude": 85.3240,
"source": "APP", "status": "active", "created_at": "..."
},
"guardians_notified": 3
}Guardian broadcasts include GUARDIANS_PINGED with the incident and guardian IDs.
SQLAlchemy models live in backend/model/database_models.py.
- User β phone, email, name, age, optional NID, role, identity file paths, verification/active state, timestamps. Roles:
USER,GUARDIAN,DISPATCHER,ADMIN. - RegistrationSession β multi-step registration state before a real
Useris created (profile fields, uploads, guardian cert fields, completion, expiry). - Guardian β links to a user; certificate data, approval state, availability, current location, last update, response metrics.
- Incident β emergency type, coordinates, source (
APP/SMS), reporter relationship, status, description, assignment, response times. Statuses:ACTIVE,IN_PROGRESS,RESOLVED,CANCELLED. Types:Medical,Police,Fire,General,Natural Disaster,Accident. - GuardianResponse β a guardian's response (
ACCEPTED,DECLINED,ARRIVED,COMPLETED) with estimated arrival, response location, notes.
Configured in backend/app/main.py, in order:
| Middleware | Responsibility |
|---|---|
RequestIDMiddleware |
Adds/preserves X-Request-ID |
TimingMiddleware |
Request timing; logs slow requests |
LoggingMiddleware |
Logs method, path, status, IP, request ID, duration |
RateLimiterMiddleware |
In-memory per-IP limits (60/min, 1000/hr) |
APIMetricsMiddleware |
Request totals, status counts, response times, error rates |
APIVersioningMiddleware |
Defaults to API version 1 |
RequestValidationMiddleware |
Content type, size, JSON syntax, SQLi/XSS patterns |
CompressionMiddleware |
gzip for larger responses |
ErrorHandlerMiddleware |
Consistent JSON errors for unhandled exceptions |
CORSMiddleware |
Permits any http/https origin in development |
Rate-limiter and metrics storage are in-memory. For multi-process/multi-server deployments, back them with Redis or another shared store.
The Expo app lives in frontend/app/apps/expo-app. Entry route redirects to /(auth)/sign-up.
Authentication / onboarding: phone entry β OTP β user-type selection (citizen, guardian, volunteer, organization) β profile form & document upload β submitted/contact screens for non-citizen roles.
Online flow: map centered on the user (Kathmandu fallback) via LocationService β emergency categories in a draggable bottom sheet β selecting one routes into the emergency-selection flow.
Offline / SMS flow: select emergency type β sub-type β SOS preview loads fresh/cached location β encode SMS-{TYPE}-{GEOHASH}-{ID} β open SMS composer to the configured receiver β copy manually if the composer can't open.
Location behavior: Expo Location (foreground permission) Β· caches last location in AsyncStorage (max age 30 min) Β· prefers fresh, falls back to cached, then to Kathmandu coordinates.
Network behavior: useNetworkStatus uses @react-native-community/netinfo; offline UI stays available; location refresh is disabled while offline.
Shared workspace packages: @kit/ui, @kit/supabase, @kit/auth, @kit/account, and tooling/* shared configs. Bachao-specific screens currently live directly inside apps/expo-app/app.
Mobile app SOS flow
- User logs in and receives a JWT.
- App sends
POST /api/sos/with type, lat, lng, device ID. - Backend creates an
Incidentwith sourceAPP. - Backend finds approved, available guardians within the configured distance.
- Backend broadcasts to
/ws/incidentsand/ws/guardians. - Dispatchers/admins view it in
/api/dispatch/incidentsor the HTML dashboard. - Guardians respond via
/api/guardians/respond/{incident_id}; an accepted guardian becomes assigned.
Offline SMS flow
- User selects emergency type and sub-type in the offline UI.
- App loads fresh, cached, or fallback location.
- App encodes
SMS-{TYPE}-{GEOHASH}-{ID}. - App opens the SMS composer to the configured forwarding receiver.
- SMS gateway posts the received message to
/api/sms/ingress. - Backend decodes the message and creates an
SMSincident. - Backend broadcasts incident and guardian ping events.
Dashboard assignment flow
- Admin opens
/api/backend-dashboard/incoming-reports. - Dashboard loads incidents and available approved guardians.
- Admin posts
incident_id+guardian_idto/api/backend-dashboard/assign-guardian. - Backend sets the incident to
IN_PROGRESSand marks the guardian unavailable. - Admin can unassign later β incident returns to
ACTIVE, guardian becomes available again.
Backend-only build (recommended for now):
docker build -t bachao-backend ./backend
docker run --rm -p 8000:8000 bachao-backendThe root
docker-compose.ymldefinesbackendandfrontend-dev, butfrontend-devreferences a missing./frontend/Dockerfile.dev. The full compose file won't build until that Dockerfile is added or the frontend service is removed.
Use backend/assign_admin.py to inspect users and update roles in SQLite (run from backend):
python assign_admin.py --list
python assign_admin.py --user-id 1
python assign_admin.py --phone 9812345678
python assign_admin.py --role DISPATCHER --user-id 1
python assign_admin.py --remove-admin 1After changing a role, the user must log in again so their JWT carries the new role.
# Backend
cd backend && python -m pytest
# Frontend
cd frontend/app/apps/expo-app
pnpm test && pnpm typecheck && pnpm lintManual backend checks:
curl http://localhost:8000/
curl http://localhost:8000/api/health
curl http://localhost:8000/api/metrics/summaryManual SMS ingress check:
curl -X POST http://localhost:8000/api/sms/ingress ^
-H "Content-Type: application/json" ^
-d "{\"sender\":\"+9779812345678\",\"body\":\"SMS-M-tudr4et3f9-X9K2Q1\"}"PowerShell:
Invoke-RestMethod `
-Method Post `
-Uri http://localhost:8000/api/sms/ingress `
-ContentType "application/json" `
-Body '{"sender":"+9779812345678","body":"SMS-M-tudr4et3f9-X9K2Q1"}'There is no complete backend test suite yet;
test_main.httpholds only scratch requests (including a stale/hello/Userrequest the app doesn't implement).
- Both
BachaoandBachauspellings appear across names, scripts, and package metadata. - Root npm frontend scripts don't match the current frontend folder layout.
docker-compose.ymlreferences a missingfrontend/Dockerfile.dev.- The backend database URL is hardcoded in
backend/core/database.py. - Dashboard routes are currently unauthenticated.
- Dev OTPs are hardcoded:
123456(registration step 1) and1234(login). - Some frontend API constants reference endpoints not yet implemented (
/sms/decode,/sms/emergency,/sos/{id},/guardians/{id}). packages/sms-core/spec.mdsays onlySOSis required, while the app usesSMSand the decoder accepts both.- Runtime artifacts are present in the checkout even though
.gitignoreexcludes them. - No migration system yet β tables are created on startup via SQLAlchemy
create_all.
- Replace hardcoded OTP checks with a real OTP provider
- Use a strong
SECRET_KEYand rotate it safely - Move database configuration fully into environment-driven settings
- Add migrations (Alembic or similar)
- Protect dashboard routes with proper admin authentication
- Replace in-memory rate limiting and metrics with shared infrastructure
- Restrict CORS origins
- Store uploads in durable object storage or a managed volume
- Add request auditing for admin actions
- Add automated backend tests (registration, login, incident creation, guardian approval/response, SMS decoding)
- Align frontend API constants with the backend API surface
- Remove generated/runtime files from source control
Bachao Β· Built to reach help when the network can't. π
