Table of Contents
CrisisEcho: Project Checkpoint Report
LLM-Augmented Early Detection of Local Emergencies from Social Media
Samuel Enam Zih — Department of Computer Science, Virginia Tech
Download
CRISISECHO_PROGRESS_REPORT.pdfAbstract
This report documents the complete implementation of CrisisEcho, a real-time crisis detection and emergency response platform. CrisisEcho ingests eight social media and official data sources via Apache Kafka, processes them through an eight-step ML preprocessing pipeline (text cleaning, GPS geocoding, MinHash deduplication, DistilBERT relevance filtering, Vertex AI text embeddings, SigLIP image embeddings, S3 image upload, and MongoDB persistence), and runs a three-step LangChain LLM agent that clusters posts into distinct crisis events, scores severity, verifies through multi-source corroboration, and generates public alerts. The system is built as a two-service architecture: a Go Fiber REST API with 20 domain modules and WebSocket support, and a Python AI sidecar with Celery task queues and gRPC. Data is stored across three MongoDB Atlas databases, Aiven Valkey (Redis-compatible) for real-time pub/sub, and AWS S3 for media. A peer-to-peer SOS emergency system with proximity-based wave broadcasting, Apple VoIP push notifications via CallKit, real-time WebSocket location tracking, and AES-256-GCM encrypted chat provides active emergency response capabilities. The Flutter mobile application comprises 24+ screens including an interactive crisis map, SOS interface, community reports, analytics dashboard, and Stripe billing. All components are fully implemented, containerized via Docker Compose, and pending deployment to Google Cloud Run.
I. Introduction
Official emergency channels—911 dispatch, government sensors, police scanners—frequently lag fast-moving crises by minutes or hours. Social media posts constitute the largest real-time sensor network on Earth: ordinary people post hyperlocal signals well before any official report. CrisisEcho closes this gap by applying Retrieval-Augmented Generation (RAG) and multi-step LLM agents to detect, classify, verify, and summarize crisis events from social media and official data sources in near real-time.
Motivation
In the critical first minutes of an emergency, the gap between on-the-ground reality and official response can be fatal. Existing crisis informatics tools are either keyword-based (failing on indirect language and sarcasm), researcher-facing (not accessible to ordinary users), or limited to single event types. CrisisEcho addresses all three limitations with semantic RAG-powered reasoning, a consumer-facing mobile application, and coverage of 51 crisis categories.
Problem Statement
Build a system that:
- Continuously ingests social and official data sources
- Identifies genuine crisis events using semantic understanding rather than keyword matching
- Verifies events through multi-source corroboration to eliminate false positives
- Delivers verified, geolocated alerts to mobile users in near real-time
- Provides a peer-to-peer SOS mechanism for users in immediate danger
Significance
CrisisEcho demonstrates four paradigm shifts over traditional crisis detection: text to semantics (vector embeddings replace keyword filters), retrieval to reasoning (hybrid retrieval feeds chain-of-thought LLM agents), vertical to multi-domain (51 categories from wildfires to epidemics), and closed-world to open-world generalization (the LLM recognizes novel crisis types without retraining). The SOS system extends the platform from passive monitoring to active emergency response.
III. System Architecture Overview
CrisisEcho is implemented as a two-service architecture: a Go Fiber HTTP API (port 8080) serving the Flutter mobile frontend, and a Python AI sidecar (port 8081 HTTP, port 8082 gRPC) handling all machine learning workloads. The services communicate via HTTP and gRPC, and share state through three MongoDB databases and Redis.


Architectural Progression
Several significant pivots were made during development:
- Frontend: Next.js + Leaflet.js → Flutter + Google Maps (native mobile performance, single codebase for iOS/Android)
- LLM: Claude Haiku → Gemini 2.0 Flash (free tier for continuous 60-second pipeline runs during development)
- Embeddings: Voyage AI 1024-dim → Vertex AI multimodal 1408-dim (text) + SigLIP 512-dim (image)
- Task Processing: Direct execution → Celery with two queues (ingestion + agent) via Redis broker
- Communication: HTTP only → HTTP + gRPC (for pipeline triggering and queries)
- New Subsystem: SOS emergency system with VoIP push, WebSocket tracking, and encrypted chat—not in the original proposal
Data Flow
The canonical data flow is: Raw data → Kafka (two topics) → Preprocessor (8 steps) → SourcePost (per-source collections + vector embeddings) → Retrieval (hybrid: vector + geo + official) → LLM Agent (cluster → severity → verify → alert) → Crisis (map dot) → Alert (Redis pub/sub → FCM/APNs push → mobile).
Docker Compose
The system runs as four containers:
- crisisecho-api (Go, port 8080)
- crisisecho-sidecar (Python FastAPI + gRPC + Kafka consumer, ports 8081/8082)
- crisisecho-worker-ingestion (Celery, concurrency=4)
- crisisecho-worker-agent (Celery, concurrency=2)
The Go API depends on the sidecar's health check passing before starting.
IV. Ingestion Layer
Eight Python workers poll or stream their respective data source APIs and produce messages to two Apache Kafka topics on Aiven's managed Kafka service.
Social Sources (topic: social_raw)
- RedditWorker: PRAW library streaming from crisis-related subreddits
- TwitterWorker: twscrape polling with crisis-specific search queries
- BlueskyWorker: AT Protocol firehose with keyword filtering
- RSSWorker: Configurable RSS/Atom feed polling via feedparser
Official Sources (topic: official_alerts)
- USGSWorker: USGS earthquake feed (magnitude ≥ 2.5)
- GDACSWorker: Global Disaster Alert and Coordination System (UN-backed GeoRSS)
- ReliefWebWorker: UN OCHA humanitarian crisis API
- NASAFirmsWorker: NASA FIRMS satellite wildfire detection
Each worker inherits a KafkaWorker base class providing: a stream() generator interface, envelope wrapping with topic/source/timestamp metadata, user privacy hashing (SHA-256 before Kafka serialization), and retry logic with exponential backoff (1s → 60s max, 5 attempts). Workers are registered in a WORKER_REGISTRY and can be toggled via the DISABLED_SOURCES environment variable.
Source authority weights used in retrieval ranking: USGS, GDACS, ReliefWeb, NASA FIRMS = 1.0; Reddit = 0.7; Twitter, Bluesky = 0.6; RSS = 0.5.
A SeedWorker was also built to generate realistic test data: 53 crisis scenario templates across 30+ countries produce 636 synthetic posts (12 per scenario) using Groq LLaMA 3.1 8B, with 90-minute refresh cycles and direct preprocessor injection—enabling full pipeline testing without external API dependencies.
V. Preprocessing Pipeline
The preprocessing pipeline consumes Kafka messages via the orchestrator and dispatches Celery tasks to the ingestion queue (concurrency=4). Each post passes through eight sequential stages:
VI. Retrieval and LLM Agent
A. Hybrid Retrieval
Every 60 seconds (or immediately on volume spike detection: >10 posts in 30 seconds from the same 0.5° grid cell), the orchestrator dispatches a run_pipeline Celery task to the agent queue. The HybridRetriever class executes four parallel sub-queries:
Results are merged, deduplicated by post ID, and ranked by composite score: 0.5 × vector_similarity + 0.3 × recency + 0.2 × source_authority.
B. Three-Step LLM Agent
Google Gemini 2.0 Flash (primary) or Ollama Llama3 (offline fallback) executes three LangChain LCEL chains:
C. Verification System
Three additive evidence paths determine whether a cluster becomes a verified Crisis:
Confidence is capped at 1.0. Unverified clusters still write a UnifiedPost (verified=false) for analytics, but no Crisis or Alert is created. This multi-gate design prevents false positives on the map while preserving all data for future analysis.
D. Entity Hierarchy
The entity hierarchy provides a complete audit trail: SourcePost (normalized raw post in per-source collection) → Cluster (internal LLM grouping, never exposed to frontend) → UnifiedPost (LLM-synthesized summary) → Crisis (only if verified; the map dot) → Alert (push notification via Redis pub/sub → FCM/APNs).
VII. Go API Layer
The Go API is built with Fiber v2 and follows a domain-driven structure: each entity has separate model/, repository/, service/, and controller/ packages. Table I lists all 20 domain modules.
Table I: Go API Domain Modules
| Module | Purpose |
|---|---|
| crisis | Verified crisis events (map dots) |
| unifiedpost | LLM-synthesized summaries |
| post | SourcePosts (per-source collections) |
| cluster | Internal LLM groupings |
| alert | Push notification records |
| user | User accounts + device tokens |
| auth | Firebase Auth (Google, Apple, Phone OTP) |
| sos | SOS profiles + alerts (legacy) |
| sos (session) | SOS sessions, responses, messages, contacts |
| community | Community crisis reports |
| analytics | Dashboard data + SOS analytics |
| billing | Stripe subscriptions + payment methods |
| category | 51 parent categories + 78 subcategories |
| notify | Location-based subscriptions |
| upload | S3 presigned URLs + direct upload |
| query | Natural language queries (forwards to sidecar) |
| rag | Pipeline trigger (pings sidecar every 60s) |
| ingest | Kafka consumer (logging only) |
| responder | Official responder profiles |
| location | Saved user locations |
Middleware
Three middleware components: (1) JWTAuth—verifies Firebase Auth ID tokens and app-issued JWTs, storing user context in request locals; (2) RateLimit—sliding-window per-user rate limiting with auto-cleanup; (3) RequirePlan—gates features behind billing plans (Pro/Enterprise) by checking the user's Stripe subscription.
WebSocket
Three WebSocket endpoints backed by Redis pub/sub: /ws/alerts (live crisis alert stream), /ws/sos/:sessionId (SOS location relay room), and /ws/chat/:sessionId/:helperId (SOS private encrypted chat).
VIII. SOS Emergency System
The SOS system enables a user in distress to broadcast an emergency alert to nearby opted-in helpers using an Uber-style proximity broadcast model. This was a major feature addition not in the original proposal.
A. Session Lifecycle
B. Push Notifications
iOS with VoIP token: Apple PushKit VoIP push via HTTP/2 with certificate-based TLS (.p12). This triggers a full-screen CallKit incoming-call UI with native Accept/Decline buttons—works even when the app is force-quit or the phone is locked. If the VoIP token is stale (410 BadDeviceToken), it falls through to FCM.
Android or no VoIP token: Firebase Cloud Messaging data-only push. The Flutter app builds a local notification with Accept/Decline action buttons.
Stale tokens (FCM: NOT_FOUND/UNREGISTERED; APNs: 410/BadDeviceToken) are automatically cleared from user records.
C. Real-Time Location Tracking
All SOS participants connect to /ws/sos/{sessionId}, a WebSocket room backed by Redis pub/sub channel sos:{sessionId}. Location updates are relayed in real-time with echo prevention (each connection gets a random _conn_id; messages with matching _conn_id are not relayed back to the sender). A fallback ticker polls the durable Redis key every 5 seconds, and a 15-second health ping prevents Aiven from dropping idle subscriptions.
D. Encrypted Chat
Each sender–helper pair communicates via /ws/chat/{sessionId}/{helperId}. Messages are encrypted at rest using AES-256-GCM: a random 12-byte nonce is prepended to the ciphertext, and the result is base64-encoded for MongoDB storage. The encryption key is a 32-byte value from the SOS_ENCRYPTION_KEY environment variable. Messages are auto-purged 24 hours after session resolution.
IX. Flutter Mobile Application
The frontend is a Flutter mobile application targeting iOS and Android from a single codebase. It uses Riverpod for state management, Go Router for navigation, Dio for HTTP with JWT interceptors, and Flutter Secure Storage for credential persistence.
Fig. 3 shows the crisis map and drill-down screens. The primary screen displays an interactive Google Maps view with severity-colored crisis dots and category-specific SVG icons spanning 51 parent categories. Tapping a dot reveals the LLM-generated analysis summary with confidence scores, contributor counts, and official corroboration badges.





Fig. 3. Crisis Map and Details.
Fig. 4 shows the SOS emergency system screens. The sender triggers an SOS, sees the wave broadcast progress, and once helpers accept, all participants appear on a shared live map. Private encrypted chat is available between the sender and each helper. On iOS, incoming SOS requests appear as full-screen CallKit alerts.






Fig. 4. SOS Emergency System.
Fig. 5 shows community reports, analytics, and profile management screens. Users can submit crisis reports with images, view analytics dashboards (plan-gated), and manage their profiles.






Fig. 5. Community, Analytics, and Profile.
Fig. 6 shows location management and billing screens. Users can save locations for custom alert radii, and subscribe to Pro or Enterprise plans via Stripe.






Fig. 6. Locations, Billing, and Settings.
X. Database Design
Three MongoDB Atlas databases serve distinct access patterns, avoiding contention between operational CRUD, vector similarity search, and location enrichment workloads.
XI. Key Design Decisions
XII. Deployment Architecture
Current (Local)
Docker Compose with four containers sharing a .env file. The Go API depends on the Python sidecar's health check. Aiven Kafka, Aiven Valkey, and MongoDB Atlas are external managed services—the same URIs work in both local Docker and cloud deployment.
Target (Cloud)
Two Google Cloud Run services:
- crisisecho-api: Go binary, port 8080, environment variable PYTHON_SIDECAR_URL pointing to the sidecar's Cloud Run URL.
- crisisecho-sidecar: Python, ports 8081 (HTTP) + 8082 (gRPC), environment variable GO_API_BASE pointing to the API's Cloud Run URL.
Both services are deployed as separate Cloud Run instances. The Flutter app will be published to the Apple App Store.
XIII. Current Status and Remaining Work
Completed
- Go API: 20 domain modules, 3 middleware, 3 WebSocket endpoints, Firebase Auth, FCM, APNs VoIP push
- Python Sidecar: 8 ingestion workers, 8-step preprocessor, hybrid retriever, 3-step LLM agent, crisis verifier, Celery (2 queues), gRPC server, FastAPI, volume spike detection
- Databases: 3 MongoDB Atlas databases with 30+ collections, 2dsphere and Atlas Vector Search indexes, Redis pub/sub channels
- Frontend: Flutter app with 24+ screens (crisis map, SOS, community reports, analytics, billing, profiles, locations)
- Infrastructure: Docker Compose (4 containers), Kafka topics, S3 media storage, Firebase configuration
Remaining
- Deploy Go API to GCP Cloud Run
- Deploy Python sidecar to GCP Cloud Run
- Publish Flutter app to Apple App Store
XIV. Task Assignment
All work was completed by a single developer. Table II details responsibilities.
Table II: Task Assignment
| Member | Responsibilities |
|---|---|
| S. Zih | Backend: Go Fiber API (20 modules), Python sidecar (LLM pipeline, preprocessing, 8 ingestion workers, Celery, gRPC), MongoDB (3 databases, 30+ collections), Kafka, Redis, WebSocket, APNs VoIP, FCM, Docker; Frontend: Flutter (24+ screens), Google Maps, SOS tracking, encrypted chat, Stripe billing; AI/ML: DistilBERT relevance, SigLIP embeddings, Vertex AI embeddings, LangChain agent, verification; Report: all sections |
XV. Schedule
Table III compares the originally planned schedule with actual progress.
Table III: Project Schedule—Planned vs. Actual
| Weeks | Planned | Actual |
|---|---|---|
| 1–2 | Literature review; provision Atlas, Kafka, Redis; define schemas | Completed as planned |
| 3–4 | Ingestion workers; spaCy + geocoding; DistilBERT; embeddings | Completed; added SigLIP embeddings, Celery |
| 5–6 | Hybrid retrieval; LangChain 3-step agent | Completed; switched LLM to Gemini 2.0 Flash |
| 7–8 | Cluster persistence; Redis Pub/Sub; Go Fiber API | Completed; added gRPC, 20 domain modules |
| 9–10 | Next.js frontend; evaluation | Pivoted to Flutter; built 24+ screens including SOS |
| 11–12 | Stretch features; system testing | Built full SOS system with VoIP push, billing, community reports, categories |
| 13 | Documentation; demo | Cloud Run deployment (in progress); App Store submission pending |
References
- M. Imran, P. Mitra, and C. Castillo, "Twitter as a lifeline: Human-annotated Twitter corpora for NLP of crisis-related messages," in Proc. LREC, 2016.
- A. Olteanu, C. Castillo, F. Diaz, and S. Vieweg, "CrisisLex: A lexicon for collecting and filtering microblogged communications in crises," in Proc. ICWSM, 2014.
- F. Alam, F. Ofli, and M. Imran, "HumAID: Human-annotated disaster incidents data from Twitter," in Proc. ICWSM, 2021.
- S. Middleton, L. Middleton, and S. Modafferi, "Real-time crisis mapping of natural disasters using social media," IEEE Intell. Syst., vol. 29, no. 2, pp. 9–17, 2014.
- P. Lewis, E. Perez, A. Piktus, F. Petroni, V. Karpukhin, N. Goyal, H. Küttler, M. Lewis, W. Yih, T. Rocktäschel, S. Riedel, and D. Kiela, "Retrieval-augmented generation for knowledge-intensive NLP tasks," in Proc. NeurIPS, 2020.
- S. Yao, J. Zhao, D. Yu, N. Du, I. Shafran, K. Narasimhan, and Y. Cao, "ReAct: Synergizing reasoning and acting in language models," in Proc. ICLR, 2023.
- V. Karpukhin, B. Oğuz, S. Min, P. Lewis, L. Wu, S. Edunov, D. Chen, and W. Yih, "Dense passage retrieval for open-domain question answering," in Proc. EMNLP, 2020.
- T. Sakaki, M. Okazaki, and Y. Matsuo, "Earthquake shakes Twitter users: Real-time event detection by social sensors," in Proc. WWW, 2010.
- J. P. de Albuquerque, B. Herfort, A. Brenning, and A. Zipf, "A geographic approach for combining social media and authoritative data towards identifying useful information for disaster management," Int. J. Geogr. Inf. Sci., vol. 29, no. 4, pp. 667–689, 2015.
Members

Samuel Enam Zih