# Project export: SafeSearch

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: OpenAI Build Week
- Tagline: AI emergency locator and triage system that prioritizes SOS signals and locates offline victims using dynamic QR codes and SMS failover to optimize rescue times.
- Devpost: https://devpost.com/software/safesearch
- GitHub: https://github.com/RajBarot3826/LifeSignal
- Video: https://www.youtube.com/embed/ILvsG6Bfwjs?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 0 GitHub contributor(s) — 

## Devpost submission (written by the team)

### Overview

💡

### Inspiration

During natural disasters, two critical problems arise: cellular network infrastructure frequently collapses, and search-and-rescue teams are overwhelmed with distress signals without knowing who needs help most urgently. We built SafeSearch to bridge this gap, ensuring that victims can broadcast their location and medical status even without internet, while rescue teams can instantly triage and prioritize operations based on real-time urgency and data. 🚀

### What it does

SafeSearch is a full-stack, AI-prioritized disaster response and victim locator system featuring: AI Triage & Prioritization: A fast triage engine categorizes incoming messages based on victim count, injuries, and entrapment status, ranking them dynamically (Critical, High, Medium, Low) to ensure rescuers help the most vulnerable first. Offline SOS Mode: When cell towers are down, the system shifts to a zero-network protocol. It compiles vital medical info (blood group, conditions) and coordinates into a high-density, easily scannable QR code for rescuers or drones, or prompts the user to send a formatted SMS. Live Interactive Map: Plots real-time victim coordinates alongside live global satellite feeds (wildfires, storms, earthquakes, volcanoes) fetched directly from the NASA EONET API. Real-Time Tracking & Live Feed: Uses WebSockets to connect victims and rescuers. Victims get real-time tracking of their assigned rescue team’s distance and ETA. Rescuer & Admin Dashboards: Command dashboards for rescuers to navigate, play on-scene video feeds, and manage rescue statuses, coupled with a central analytics center for government administrators. 🛠️

### How we built it

We engineered SafeSearch as a full-stack distributed system: Frontend: Built using React, Vite, and styled with TailwindCSS. Maps are rendered using React-Leaflet and OpenStreetMap. Backend API: Powered by Node.js & Express handling user authentication and data management. Real-time, bi-directional messaging is powered by Socket.io. Database: MongoDB with Mongoose schemas stores user profiles, SOS alerts, and rescue task logs. AI Triage Service: Built with FastAPI (Python), utilizing regex-based text analysis and keyword parsing to calculate severity scores and generate context-aware first-aid instructions. 🚧

### Challenges we ran into

Robust Geolocation: GPS can fail or be blocked. We solved this by creating a multi-layer fallback: trying browser GPS first, falling back to IP-based location services, and finally letting the user manually input landmarks or coordinates. Network-less SOS Transmission: Developing a way to package and send data with zero internet. We resolved this by compressing vital payload data into structured JSON and embedding it into high-density dynamic QR codes. 🎉

### Accomplishments we're proud of

Designing a complete offline-to-online safety loop using QR codes and SMS redirects. Integrating live satellite feeds from NASA EONET in real time. Creating a clean, modern user interface that feels premium, highly readable, and functional under stress. 🧠

### What we learned

How to design applications for high-reliability under extreme environmental constraints. Managing client-side offline states dynamically in React. 🔮

### What's next

Edge LLMs: Migrating the FastAPI keyword parser to a localized, lightweight LLM running directly on the victim's device. LoRaWAN Integration: Allowing SOS data transmission over long-range, low-power radio networks. Drone Scanning: Integrating computer-vision drone routing to automatically fly over affected areas and scan QR codes displayed on victim screens from the air.

## README (from the GitHub repository)

# AI Disaster Response & Victim Locator System

This is a full-stack, AI-powered system designed to categorize and prioritize emergency SOS signals to help rescue teams respond efficiently.

## Prerequisites
- **Node.js**: v18 or later
- **Python**: v3.9 or later
- **MongoDB**: Must be running locally on port `27017`

---

## 🚀 How to Run the Project Locally

You must start **three** separate servers in three different terminal windows.

### 1. Start the React Frontend
This is what the Victims and Admin/Rescuers see.
```bash
cd frontend
npm install
npm run dev
```
It will start on `http://localhost:5173` or `http://localhost:5174`.

### 2. Start the Node.js Backend
This handles the database, User Authentication, and Real-time WebSocket tracking.
```bash
cd backend
npm install
node index.js
```
It will start on `http://localhost:5000`.

### 3. Start the Python AI Service
This analyzes the victim's SOS text to determine injuries, counts, and priority levels.
```bash
cd ai-service
# It is recommended to create a virtual environment first:
# python -m venv venv
# .\venv\Scripts\activate
pip install -r requirements.txt
uvicorn app:app --host 0.0.0.0 --port 8000
```
It will start on `http://localhost:8000`.

---

## 🌐 How to Share this Project With Friends

If you want your friends to connect to your app from their own phones or computers, you cannot just send them `localhost` links. 

### Option A: Let them connect to your Wi-Fi
If your friends are on the **same Wi-Fi network** as your laptop:
1. Find your laptop's IPv4 address (Open Command Prompt and type `ipconfig`. Look for IPv4 Address, e.g., `192.168.1.5`).
2. Run the frontend exposing your IP: 
   `npm run dev -- --host 0.0.0.0`
3. Tell your friends to open their browser and go to your IP address on the frontend port, e.g., `http://192.168.1.5:5174`.
*(Note: You will need to change the API URLs from `localhost` in your React code to your IPv4 address for them to connect to your backend).*

### Option B: Use Ngrok (Easiest for Remote Friends)
If your friends are located entirely somewhere else:
1. Download [Ngrok](https://ngrok.com/).
2. Start your frontend normally.
3. Open a new terminal and run: `ngrok http 5174` (or whatever port your frontend is on).
4. Ngrok will generate a public URL like `https://a1b2c3d4.ngrok.app`.
5. Send that URL to your friends!

### Option C: Zip the Code and send it
If you want them to run it completely on their own machines:
1. Delete the `node_modules` folders in `frontend` and `backend`.
2. Delete the `venv` folder in `ai-service`.
3. Right-click the root `LifeSignal` folder and select "Compress to ZIP file".
4. Send them the ZIP file and ask them to follow the **How to Run** instructions above!


## Detected evidence (automated analysis)

Indexed codebase: 37 recognized source files, 494 KB.
- CSS (language) — detected in the code
- Express (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- MongoDB (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- FastAPI (technology) — claimed on Devpost, not found in the code
- Node.js (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (41 of 41)

```
ai-service/app.py
backend/.env
backend/index.js
backend/middleware/auth.js
backend/models/RescueTask.js
backend/models/SOSReport.js
backend/models/User.js
backend/package.json
backend/routes/auth.js
backend/routes/rescue.js
backend/routes/sos.js
frontend/.gitignore
frontend/dev-dist/registerSW.js
frontend/dev-dist/sw.js
frontend/dev-dist/workbox-5a5d9309.js
frontend/dev-dist/workbox-be266a9d.js
frontend/dev-dist/workbox-f7c39696.js
frontend/eslint.config.js
frontend/index.html
frontend/package.json
frontend/postcss.config.js
frontend/README.md
frontend/src/App.css
frontend/src/App.jsx
frontend/src/components/Navbar.jsx
frontend/src/components/OfflineSOS.jsx
frontend/src/context/AuthContext.jsx
frontend/src/hooks/useNetworkStatus.js
frontend/src/index.css
frontend/src/main.jsx
frontend/src/pages/AdminDashboard.jsx
frontend/src/pages/Home.jsx
frontend/src/pages/LiveMap.jsx
frontend/src/pages/Login.jsx
frontend/src/pages/Register.jsx
frontend/src/pages/RescueDashboard.jsx
frontend/src/pages/SendSOS.jsx
frontend/tailwind.config.js
frontend/vite.config.js
README.md
test-sms.js
```

### Dependencies

- backend/package.json: axios@^1.13.6, bcryptjs@^3.0.3, cors@^2.8.6, dotenv@^17.3.1, express@^5.2.1, jsonwebtoken@^9.0.3, mongoose@^9.3.0, multer@^2.1.1, nodemon@^3.1.14, socket.io@^4.8.3
- frontend/package.json: @eslint/js@^9.39.1, @types/react@^19.2.7, @types/react-dom@^19.2.3, @vitejs/plugin-basic-ssl@^2.2.0, @vitejs/plugin-react@^5.1.1, autoprefixer@^10.4.27, axios@^1.13.6, eslint@^9.39.1, eslint-plugin-react-hooks@^7.0.1, eslint-plugin-react-refresh@^0.4.24, globals@^16.5.0, leaflet@^1.9.4, lucide-react@^0.577.0, postcss@^8.5.8, qrcode.react@^4.2.0, react@^19.2.0, react-dom@^19.2.0, react-leaflet@^5.0.0, react-router-dom@^7.13.1, socket.io-client@^4.8.3, tailwindcss@^3.4.19, vite@^7.3.1, vite-plugin-pwa@^1.2.0

### Recent commits (newest first)

- Initial commit

## Key source files (fetched from GitHub, selected and truncated for size)

### backend/package.json

```
{
  "name": "backend",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "type": "commonjs",
  "dependencies": {
    "axios": "^1.13.6",
    "bcryptjs": "^3.0.3",
    "cors": "^2.8.6",
    "dotenv": "^17.3.1",
    "express": "^5.2.1",
    "jsonwebtoken": "^9.0.3",
    "mongoose": "^9.3.0",
    "multer": "^2.1.1",
    "socket.io": "^4.8.3"
  },
  "devDependencies": {
    "nodemon": "^3.1.14"
  }
}

```

### frontend/package.json

```
{
  "name": "frontend",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "lint": "eslint .",
    "preview": "vite preview"
  },
  "dependencies": {
    "axios": "^1.13.6",
    "leaflet": "^1.9.4",
    "lucide-react": "^0.577.0",
    "qrcode.react": "^4.2.0",
    "react": "^19.2.0",
    "react-dom": "^19.2.0",
    "react-leaflet": "^5.0.0",
    "react-router-dom": "^7.13.1",
    "socket.io-client": "^4.8.3"
  },
  "devDependencies": {
    "@eslint/js": "^9.39.1",
    "@types/react": "^19.2.7",
    "@types/react-dom": "^19.2.3",
    "@vitejs/plugin-basic-ssl": "^2.2.0",
    "@vitejs/plugin-react": "^5.1.1",
    "autoprefixer": "^10.4.27",
    "eslint": "^9.39.1",
    "eslint-plugin-react-hooks": "^7.0.1",
    "eslint-plugin-react-refresh": "^0.4.24",
    "globals": "^16.5.0",
    "postcss": "^8.5.8",
    "tailwindcss": "^3.4.19",
    "vite": "^7.3.1",
    "vite-plugin-pwa": "^1.2.0"
  }
}

```

### backend/index.js

```javascript
require('dotenv').config();
const express = require('express');
const http = require('http');
const mongoose = require('mongoose');
const cors = require('cors');
const { Server } = require('socket.io');
const bcrypt = require('bcryptjs');
const User = require('./models/User');

const app = express();
const server = http.createServer(app);
const io = new Server(server, {
  cors: { origin: '*' }
});

const authRoutes = require('./routes/auth');
const sosRoutes = require('./routes/sos');
const rescueRoutes = require('./routes/rescue');

app.use(cors());
app.use(express.json());
app.use('/uploads', express.static('uploads'));

app.set('io', io);

// Routes
app.use('/api/auth', authRoutes);
app.use('/api/sos', sosRoutes);
app.use('/api/rescue', rescueRoutes);

// Database connection
const seedAdmin = async () => {
  try {
    const adminExists = await User.findOne({ email: 'admin@gov.in' });
    if (!adminExists) {
      const salt = await bcrypt.genSalt(10);
      const hashedPassword = await bcrypt.hash('admin123', salt);
      const adminUser = new User({
        name: 'Government Admin',
        email: 'admin@gov.in',
        password: hashedPassword,
        role: 'Admin'
      });
      await adminUser.save();
      console.log('Default Government Admin account seeded successfully (admin@gov.in / admin123)');
    }
  } catch (err) {
    console.error('Error seeding admin account:', err);
  }
};

mongoose.connect(process.env.MONGODB_URI || 'mongodb://127.0.0.1:27017/lifesignal')
  .then(() => {
    console.log('MongoDB Connected');
    seedAdmin();
  })
  .catch(err => console.error(err));


io.on('connection', (socket) => {
  console.log('A client connected:', socket.id);
  socket.on('disconnect', () => {
    console.log('Client disconnected:', socket.id);
  });
});

const PORT = process.env.PORT || 5000;
server.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

```

### ai-service/app.py

```python
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import re

app = FastAPI(title="LifeSignal AI Service")

class SOSRequest(BaseModel):
    message: str

class SOSResponse(BaseModel):
    injury_detected: bool
    has_critical_injury: bool
    has_vulnerable: bool
    is_trapped: bool
    people_count: int
    situation: str
    urgency_level: str
    priority_score: int
    first_aid_instructions: str

def extract_people_count(text: str) -> int:
    # Look for numbers near words like people, of us, persons, individuals
    match = re.search(r'(\d+)\s*(people|persons|of us|kids|children|adults|injured)', text.lower())
    if match:
        return int(match.group(1))
    
    # Check for text numbers
    words = text.lower().split()
    number_words = {'one':1, 'two':2, 'three':3, 'four':4, 'five':5, 'six':6, 'seven':7, 'eight':8, 'nine':9, 'ten':10}
    for word in words:
        if word in number_words:
            return number_words[word]
            
    # Default to 1 if we can't tell, or if text implies plural but no number, maybe 2
    if 'we are' in text.lower() or 'us ' in text.lower():
        return 2
    return 1

def analyze_message(text: str) -> SOSResponse:
    text_lower = text.lower()
    
    # 1. Critical Injury detection
    injury_keywords = ['injured', 'bleeding', 'hurt', 'broken', 'blood', 'unconscious', 'medical', 'pain', 'heart attack', 'critical']
    injury_detected = any(kw in text_lower for kw in injury_keywords)
    has_critical_injury = injury_detected # Map any injury to the requested critical injury pool for demo purposes
    
    # 2. Kids/Elderly detection
    vulnerable_keywords = ['child', 'children', 'kid', 'kids', 'baby', 'elderly', 'old', 'mother', 'father', 'grandparent']
    has_vulnerable = any(kw in text_lower for kw in vulnerable_keywords)
    
    # 3. Trapped detection
    trapped_keywords = ['trapped', 'stuck', 'under', 'rubble', 'cant move', "can't move", 'collapsed']
    is_trapped = any(kw in text_lower for kw in trapped_keywords)
    
    # General situation categorization
    fire_keywords = ['fire', 'burning', 'smoke', 'flames']
    flood_keywords = ['flood', 'water', 'drowning', 'submerged']
    
    situationList = []
    if is_trapped: situationList.append('Trapped')
    if any(kw in text_lower for kw in fire_keywords): situationList.append('Fire')
    if any(kw in text_lower for kw in flood_keywords): situationList.append('Flood')
    if injury_detected: situationList.append('Medical Emergency')
    
    situation = ", ".join(situationList) if situationList else "General Emergency"
    
    # 4. People Count
    people_count = extract_people_count(text)
    
    # 5. Strict Priority Scoring based on requested rules
    # 1. Critical injury (Massive boost)
    # 2. Children / elderly (High boost)
    # 3. Trapped victims (Medium boost)
    score = 0
    
    if has_critical_injury: score += 60
    if has_vulnerable: score += 30
    if is_trapped: score += 15
    
    # People count minor booster
    score += min(people_count * 2, 10)
    
    # Cap at 100
    score = min(score, 100)
    
    # 6. Urgency Level
    if score >= 60 or has_critical_injury:
        urgency = "CRITICAL"
    elif score >= 30 or has_vulnerable:
        urgency = "HIGH"
    elif score >= 15 or is_trapped:
        urgency = "MEDIUM"
    else:
        urgency = "LOW"
        
    # 7. First Aid Instructions
    first_aid_instructions = ""
    if has_critical_injury:
        if "bleeding" in text_lower or "blood" in text_lower:
            first_aid_instructions = "Apply firm, direct pressure to the wound with a clean cloth. Keep the injured area elevated above the heart if possible. Do not remove the cloth if blood soaks through, add another on top."
        elif "broken" in text_lower or "bone" in text_lower:
            first_aid_instructions = "Do NOT attempt to realign the bone. Immobilize the injured area using a makeshift splint and keep the victim still. Apply ice packs if available."
        elif "burn" in text_lower or "fire" in text_lower:
            first_aid_instructions = "Cool the burn with cool (not ice cold) running water for at least 10 minutes. Cover the burn loosely with sterile, non-fluffy dressing or cling film."
        else:
            first_aid_instructions = "Keep the injured person perfectly still. Do not move them unless they are in immediate, life-threatening danger. Check their breathing and wait for the Rescue Team."
            
    return SOSResponse(
        injury_detected=injury_detected,
        has_critical_injury=has_critical_injury,
        has_vulnerable=has_vulnerable,
        is_trapped=is_trapped,
        people_count=people_count,
        situation=situation,
        urgency_level=urgency,
        priority_score=score,
        first_aid_instructions=first_aid_instructions
    )

@app.post("/analyze", response_model=SOSResponse)
async def analyze_sos(request: SOSRequest):
    try:
        result = analyze_message(request.message)
        return result
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.get("/")
def read_root():
    return {"status": "LifeSignal AI Service is running"}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

```

### frontend/src/main.jsx

```javascript
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import './index.css'
import { AuthProvider } from './context/AuthContext'
import { registerSW } from 'virtual:pwa-register'

// Register the PWA service worker immediately for offline access
registerSW({ immediate: true })
ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <AuthProvider>
      <App />
    </AuthProvider>
  </React.StrictMode>,
)

```

### frontend/src/App.jsx

```javascript
import React, { useContext } from 'react';
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
import { AuthContext } from './context/AuthContext';
import Home from './pages/Home';
import Login from './pages/Login';
import Register from './pages/Register';
import SendSOS from './pages/SendSOS';
import LiveMap from './pages/LiveMap';
import RescueDashboard from './pages/RescueDashboard';
import AdminDashboard from './pages/AdminDashboard';
import Navbar from './components/Navbar';

const ProtectedRoute = ({ children, allowedRoles }) => {
  const { user } = useContext(AuthContext);
  if (!user) return <Navigate to="/login" />;
  if (allowedRoles && !allowedRoles.includes(user.role)) return <Navigate to="/" />;
  return children;
};

function App() {
  return (
    <Router>
      <div className="flex flex-col min-h-screen">
        <Navbar />
        <main className="flex-grow">
          <Routes>
            <Route path="/" element={<Home />} />
            <Route path="/login" element={<Login />} />
            <Route path="/register" element={<Register />} />
            <Route path="/sos" element={<SendSOS />} />
            <Route path="/map" element={<LiveMap />} />
            
            <Route 
              path="/rescue" 
              element={
                <ProtectedRoute allowedRoles={['Admin']}>
                  <RescueDashboard />
                </ProtectedRoute>
              } 
            />
            <Route 
              path="/admin" 
              element={
                <ProtectedRoute allowedRoles={['Admin']}>
                  <AdminDashboard />
                </ProtectedRoute>
              } 
            />
          </Routes>
        </main>
      </div>
    </Router>
  );
}

export default App;

```

### test-sms.js

```javascript
const http = require('http');

const postData = JSON.stringify({
  Body: "SOS! Name: John Offline, Msg: I have no internet, Loc: 51.5074,-0.1278",
  From: "+19876543210"
});

const options = {
  hostname: 'localhost',
  port: 5000,
  path: '/api/sos/sms-webhook',
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Content-Length': Buffer.byteLength(postData)
  }
};

console.log("🚀 Sending simulated SMS SOS to your backend...");

const req = http.request(options, (res) => {
  console.log(`\n✅ STATUS: ${res.statusCode}`);
  res.setEncoding('utf8');
  res.on('data', (chunk) => {
    console.log(`✅ RESPONSE: ${chunk}`);
  });
  res.on('end', () => {
    console.log('\n🎉 Success! The SOS was received by your backend. Check your LiveMap to see it!');
  });
});

req.on('error', (e) => {
  console.error(`\n❌ ERROR: Could not connect to backend. Is your server running on port 5000?\nDetails: ${e.message}`);
});

req.write(postData);
req.end();

```

### frontend/postcss.config.js

```javascript
export default {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}

```

### frontend/index.html

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <link rel="apple-touch-icon" href="https://cdn-icons-png.flaticon.com/512/3208/3208759.png">
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="theme-color" content="#ef4444" />
    <meta name="description" content="LifeSignal Emergency SOS System" />
    <link rel="manifest" href="/manifest.webmanifest" />
    <title>LifeSignal SOS</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
  </body>
</html>

```

### frontend/eslint.config.js

```javascript
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import { defineConfig, globalIgnores } from 'eslint/config'

export default defineConfig([
  globalIgnores(['dist']),
  {
    files: ['**/*.{js,jsx}'],
    extends: [
      js.configs.recommended,
      reactHooks.configs.flat.recommended,
      reactRefresh.configs.vite,
    ],
    languageOptions: {
      ecmaVersion: 2020,
      globals: globals.browser,
      parserOptions: {
        ecmaVersion: 'latest',
        ecmaFeatures: { jsx: true },
        sourceType: 'module',
      },
    },
    rules: {
      'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
    },
  },
])

```

[24 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]