CrisisDesk AI is a backend-only emergency and public-service request triage API. It accepts unstructured citizen reports in Bangla, English, or an unknown language; validates and stores them; uses AI to classify the incident and estimate urgency; detects possible duplicate reports; and provides protected administrative APIs for management and analytics.
Replace these placeholders before final submission:
- Live API:
https://crisisdesk-ai-hackathon.onrender.com- GitHub repository:
https://github.com/saqib0404/crisisdesk-ai-hackathon- Postman collection:
crisisdesk-ai-hackathon.postman_collection.json
Emergency reports may be incomplete, multilingual, duplicated, or difficult to prioritize. CrisisDesk AI transforms each submitted message into a structured, searchable report.
Citizen report
↓
Zod request validation
↓
Sensitive-data redaction for AI input
↓
Gemini classification and summarization
↓
Hybrid duplicate detection
↓
Prisma persistence in PostgreSQL
↓
Structured response
↓
Protected administration and analytics
This project is intentionally backend-only.
- Public report submission
- Bangla, English, and unknown-language support
- AI-generated category, urgency, summary, suggested action, and confidence
- Safe AI fallback with manual-review marking
- Hybrid duplicate detection using description, location, category, and time
- PostgreSQL persistence through Prisma
- Filtering, free-text search, date range, sorting, and pagination
- Status management with audit history
- Category and urgency analytics
- JWT administrator authentication
- Role-based authorization
- Zod validation and structured errors
- bcrypt password hashing
- Helmet, CORS, and API rate limiting
- Docker and Docker Compose
- Render-ready deployment
- Importable Postman collection
| Layer | Technology |
|---|---|
| Runtime | Node.js |
| Language | TypeScript |
| HTTP framework | Express |
| Database | PostgreSQL |
| ORM and migrations | Prisma |
| Validation | Zod |
| AI | Google Gemini Developer API through @google/genai |
| Authentication | JSON Web Tokens |
| Password hashing | bcryptjs |
| Security | Helmet, CORS, express-rate-limit |
| Containers | Docker and Docker Compose |
| Deployment | Render with Neon PostgreSQL |
| API client | Postman |
flowchart LR
A[Postman or API Client] --> B[Express API]
B --> C[Security and Validation Middleware]
C --> D[Controllers]
D --> E[Auth and Report Services]
E --> F[Gemini AI Service]
E --> G[Duplicate Detection Service]
E --> H[Prisma Client]
H --> I[(PostgreSQL)]
D --> J[Structured JSON Response]
sequenceDiagram
participant C as Client
participant API as Express API
participant V as Zod
participant AI as Gemini
participant D as Duplicate Service
participant DB as PostgreSQL
C->>API: POST /api/reports
API->>V: Validate request
V-->>API: Validated body
API->>AI: Location, description, language
AI-->>API: Classification result
API->>D: Compare recent reports
D->>DB: Retrieve candidates
DB-->>D: Candidate reports
D-->>API: Duplicate decision
API->>DB: Store report and history
DB-->>API: Created report
API-->>C: 201 response
The database contains three main models:
Report: citizen input, AI output, duplicate information, status, and timestampsAdmin: administrator identity, role, password hash, and activity statusReportStatusHistory: report status transitions and the administrator who performed them
A report may also reference another report through matchedReportId, creating a self-relation for duplicate detection.
erDiagram
ADMIN ||--o{ REPORT_STATUS_HISTORY : performs
REPORT ||--o{ REPORT_STATUS_HISTORY : has
REPORT o|--o{ REPORT : matched_by
bn
en
unknown
medical
fire
accident
crime
flood
utility
public_service
infrastructure
other
low
medium
high
critical
pending
in_review
assigned
resolved
rejected
Base path:
https://crisisdesk-ai-hackathon.onrender.com/api
| Method | Endpoint | Access | Purpose |
|---|---|---|---|
| GET | /health |
Public | Health check |
| POST | /auth/login |
Public | Administrator login |
| GET | /auth/me |
Admin | Current administrator |
| POST | /reports |
Public, rate-limited | Submit and process a report |
| GET | /reports |
Admin | List, filter, search, sort, and paginate |
| GET | /reports/stats/summary |
Admin | Analytics summary |
| GET | /reports/:id |
Admin | Report details |
| PATCH | /reports/:id/status |
Admin | Update status and create history |
| DELETE | /reports/:id |
Super admin | Delete a report |
See docs/API_GUIDE.md for detailed requests and responses.
POST /api/reports
Content-Type: application/json{
"name": "Rahim",
"contact": "01712345678",
"location": "Sylhet Bondor Bazar",
"description": "A shop is on fire and several people may be trapped inside.",
"language": "en"
}Typical result:
{
"success": true,
"message": "Report submitted successfully.",
"data": {
"id": "cmriv59d100001sdh32l6hyuv",
"name": "Rahim",
"contact": "01712345678",
"location": "Sylhet Bondor Bazar",
"normalizedLocation": "sylhet bondor bazar",
"latitude": null,
"longitude": null,
"description": "A shop is on fire and several people may be trapped inside.",
"normalizedDescription": "a shop is on fire and several people may be trapped inside.",
"translatedDescription": null,
"language": "en",
"category": "fire",
"urgency": "critical",
"summary": "A shop is currently on fire at Sylhet Bondor Bazar, with reports suggesting people may be trapped inside.",
"suggestedAction": "Dispatch fire and rescue services immediately and initiate search and rescue operations.",
"confidence": 0.95,
"possibleDuplicate": true,
"duplicateScore": 0.8812,
"matchedReportId": "cmri65kx30002t8c0ata3r22v",
"status": "pending",
"requiresManualReview": false,
"aiStatus": "success",
"aiProvider": "google-gemini",
"aiModel": "gemini-3.1-flash-lite",
"aiMetadata": {
"reason": null,
"usedFallback": false,
"duplicateDetection": {
"breakdown": {
"timeSimilarity": 0.7571,
"categorySimilarity": 1,
"locationSimilarity": 1,
"descriptionSimilarity": 0.806
},
"threshold": 0.62,
"evaluatedCandidates": 16
}
},
"createdAt": "2026-07-13T06:50:37.861Z",
"updatedAt": "2026-07-13T06:50:37.861Z",
"matchedReport": {
"id": "cmri65kx30002t8c0ata3r22v",
"location": "Sylhet Bondor Bazar",
"description": "A shop is on fire and several people are trapped inside. .",
"category": "fire",
"urgency": "critical",
"status": "pending",
"createdAt": "2026-07-12T19:11:02.439Z"
},
"statusHistory": [
{
"id": "cmriv59ib00011sdhczyxf7w8",
"reportId": "cmriv59d100001sdh32l6hyuv",
"previousStatus": null,
"newStatus": "pending",
"changedById": null,
"note": "Report submitted and identified as a possible duplicate of cmri65kx30002t8c0ata3r22v.",
"changedAt": "2026-07-13T06:50:37.861Z"
}
]
}
}POST /api/auth/login
Content-Type: application/json{
"email": "admin@crisisdesk.local",
"password": "password1234"
}Protected endpoints require:
Authorization: Bearer <ACCESS_TOKEN>Example:
GET /api/reports?category=fire&urgency=critical&status=pending&page=1&limit=20&sortBy=createdAt&sortOrder=descSupported parameters:
| Parameter | Format |
|---|---|
category |
Allowed category |
urgency |
low, medium, high, critical |
status |
Allowed status |
language |
bn, en, unknown |
search |
Free text |
startDate |
YYYY-MM-DD |
endDate |
YYYY-MM-DD |
page |
Integer, minimum 1 |
limit |
Integer, 1–100 |
sortBy |
createdAt, updatedAt, confidence |
sortOrder |
asc, desc |
Only the following report fields are sent for classification:
- Location
- Description
- Language
The explicit name and contact fields are not sent to the AI service. Common phone numbers and email addresses found inside the description are redacted.
The AI result is constrained to the allowed categories and urgency levels, then validated again with Zod before storage.
When the AI request fails, times out, returns invalid data, or reaches a free-tier quota, the report is still stored using a safe fallback:
{
"category": "other",
"urgency": "medium",
"confidence": 0,
"aiStatus": "fallback",
"requiresManualReview": true
}Duplicate detection runs locally without a second paid API request. The weighted score is:
Description similarity 55%
Location similarity 25%
Category match 15%
Time proximity 5%
The strongest candidate must pass:
- The total-score threshold
- The minimum location-similarity threshold
- The minimum description-similarity threshold
Stored results include:
possibleDuplicateduplicateScorematchedReportId- Threshold, candidate count, and score breakdown in
aiMetadata
Duplicates point to the canonical original report rather than forming chains.
- JWT signature, expiration, issuer, audience, and role verification
- Active-administrator database check
- bcrypt password hashing
super_adminpermission for deletion- Strict Zod request validation
- Structured malformed-JSON handling
- Helmet security headers
- Configurable CORS
- General, login, and report-submission rate limits
- Request body size limit
- Sensitive-data redaction before AI processing
- Report text treated as untrusted data
- Secrets stored in environment variables and excluded from Git
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed.",
"details": [
{
"field": "body.location",
"message": "Location is required.",
"code": "invalid_type"
}
]
}
}Possible error codes include:
INVALID_JSON
VALIDATION_ERROR
REPORT_NOT_FOUND
AUTHENTICATION_REQUIRED
INVALID_ACCESS_TOKEN
INVALID_CREDENTIALS
INSUFFICIENT_PERMISSION
STATUS_UNCHANGED
ROUTE_NOT_FOUND
API_RATE_LIMIT_EXCEEDED
LOGIN_RATE_LIMIT_EXCEEDED
REPORT_RATE_LIMIT_EXCEEDED
INTERNAL_SERVER_ERROR
Create .env from .env.example. Never commit the real file.
| Variable | Required | Purpose |
|---|---|---|
NODE_ENV |
Production | Runtime mode |
PORT |
Local | HTTP port |
DATABASE_URL |
Yes | Runtime PostgreSQL connection |
DIRECT_URL |
Recommended | Direct migration connection |
GEMINI_API_KEY |
AI | Gemini credential |
GEMINI_MODEL |
AI | Selected free model |
AI_TIMEOUT_MS |
No | AI timeout |
AI_MANUAL_REVIEW_THRESHOLD |
No | Manual-review confidence threshold |
DUPLICATE_THRESHOLD |
No | Duplicate threshold |
DUPLICATE_LOOKBACK_HOURS |
No | Candidate time window |
DUPLICATE_CANDIDATE_LIMIT |
No | Candidate limit |
DUPLICATE_MIN_LOCATION_SIMILARITY |
No | Minimum location score |
DUPLICATE_MIN_DESCRIPTION_SIMILARITY |
No | Minimum description score |
JWT_SECRET |
Yes | JWT signing secret |
JWT_EXPIRES_IN_SECONDS |
No | Token lifetime |
JWT_ISSUER |
No | Token issuer |
JWT_AUDIENCE |
No | Token audience |
ADMIN_NAME |
Bootstrap | Administrator name |
ADMIN_EMAIL |
Bootstrap | Administrator email |
ADMIN_PASSWORD |
Bootstrap | Administrator password |
CORS_ORIGINS |
Production | Browser allowlist |
- Node.js and npm
- PostgreSQL
- A Gemini API key
- Docker Desktop for containerized execution
git clone <YOUR_GITHUB_REPOSITORY_URL>
cd crisisdesk-ai-hackathon
npm installCopy the environment template:
cp .env.example .envWindows PowerShell:
Copy-Item .env.example .envGenerate Prisma Client and migrate:
npx prisma generate
npx prisma migrate devSeed the administrator:
npx tsx prisma/seed.tsStart development:
npm run devVerify:
GET http://localhost:5000/api/healthStart the API and PostgreSQL:
docker compose up --buildBackground mode:
docker compose up --build -dInspect:
docker compose ps
docker compose logs -f apiStop without deleting data:
docker compose downReset all local Docker database data only when intended:
docker compose down -vThe exported Postman collection is stored in the project root:
crisisdesk-ai-hackathon.postman_collection.json
Import steps:
- Open Postman.
- Select Import.
- Select the JSON collection from the repository root.
- Open the collection's Variables tab.
- Set
baseUrltohttp://localhost:5000or the deployed URL. - Send the login request.
- The login post-response script saves the JWT in
adminToken. - Protected requests inherit collection-level Bearer authentication.
Collection variables:
{{baseUrl}}
{{adminToken}}
Use No Auth for:
- Health
- Login
- Report submission
Use Inherit auth from parent for protected endpoints.
The application is Docker-ready. The implemented deployment uses:
- Render for the web service
- Neon for PostgreSQL
- Gemini Developer API for AI processing
The production service must receive secrets through the hosting dashboard. It listens on process.env.PORT and 0.0.0.0.
Health-check path:
/api/health
Verify this order after deployment:
- Health
- Login
- Original report
- Paraphrased duplicate
- Report list
- Report details
- Status update
- Analytics
- Super-admin deletion
.
├── prisma/
│ ├── migrations/
│ ├── schema/
│ └── seed.ts
├── src/
│ ├── config/
│ ├── errors/
│ ├── generated/
│ ├── middleware/
│ ├── modules/
│ │ ├── auth/
│ │ └── reports/
│ ├── scripts/
│ ├── services/
│ ├── utils/
│ ├── app.ts
│ └── server.ts
├── docs/
│ ├── API_GUIDE.md
│ └── ARCHITECTURE.md
├── crisisdesk-ai-hackathon.postman_collection.json
├── .dockerignore
├── .env.example
├── .gitignore
├── ATTRIBUTIONS.md
├── Dockerfile
├── docker-compose.yml
├── package.json
├── package-lock.json
├── prisma.config.ts
├── README.md
└── tsconfig.json
CrisisDesk AI uses the following open-source libraries, frameworks, APIs, SDKs, platforms, and developer tools. Their authors retain all rights to their work, names, and trademarks.
| Technology | Use | Project |
|---|---|---|
| Node.js | Runtime | Node.js |
| TypeScript | Typed source | TypeScript |
| Express | HTTP API | Express |
| Prisma | Schema, migrations, ORM | Prisma |
| PostgreSQL | Database | PostgreSQL |
pg |
PostgreSQL driver | node-postgres |
@prisma/adapter-pg |
Prisma PostgreSQL adapter | Prisma |
| Zod | Validation | Zod |
| Google Gemini Developer API | AI service | Gemini API |
@google/genai |
Gemini JavaScript SDK | Google Gen AI SDK |
| bcryptjs | Password hashing | bcrypt.js |
| jsonwebtoken | JWT signing and verification | node-jsonwebtoken |
| Helmet | HTTP security headers | Helmet |
| CORS middleware | Browser-origin handling | expressjs/cors |
| express-rate-limit | Rate limiting | express-rate-limit |
| dotenv | Environment loading | dotenv |
| Docker | Containers | Docker |
| Postman | API collection | Postman |
| Render | Deployment | Render |
| Neon | Hosted PostgreSQL | Neon |
Exact installed versions and transitive dependencies are recorded in package-lock.json. Check each package's own repository and license file for its governing terms. Additional credits are available in ATTRIBUTIONS.md.
- Use fictional personal information in demonstrations.
- Do not submit real emergencies to this prototype.
- The API does not contact emergency responders.
- AI output can be incorrect and requires human judgment.
- Never commit API keys, database credentials, JWT secrets, or admin passwords.
- Rotate any secret exposed through source control, screenshots, logs, or chat.
- No frontend
- No direct dispatch, SMS, or email integration
- AI depends on the configured provider and free quota
- AI confidence is not a calibrated probability
- Duplicate detection may miss ambiguous or cross-script locations
- The duplicate algorithm is heuristic
- Free hosting may have cold starts
- Automated tests are not included in the current submission
- The prototype is not approved for operational emergency use
- Swagger/OpenAPI UI
- Automated tests
- Geocoding and coordinate comparison
- Embedding-based duplicate detection
- Notification integrations
- Administrator dashboard
- Refresh tokens
- Audit export
- Human-feedback loops
- Multilingual summaries
Team name: AI Alchemists
Team members: Saqib Ahmad, Shazid Mahmud, Kamal Hossain Parvez
Repository URL: https://github.com/saqib0404/crisisdesk-ai-hackathon
Live API URL: https://crisisdesk-ai-hackathon.onrender.com
Demo video URL:
Postman collection: crisisdesk-ai-hackathon.postman_collection.json
CrisisDesk AI is an educational hackathon prototype. It is not an official emergency service and must not be relied upon as the sole channel for reporting real emergencies.