An AI-powered routing system for WhatsApp messages built for the HackerRank Orchestrate 24-hour hackathon.
For every incoming message in dataset/messages.csv, the system decides:
| Action | Meaning |
|---|---|
notify |
Interrupt the user now — urgent or time-sensitive |
digest |
Useful but low priority — show later |
mute |
Suppress — repetitive, promotional, scam-like, or unsafe |
.
├── code/
│ ├── main.py # Router — reads dataset/, writes output.csv
│ └── evaluation/
│ └── main.py # Evaluation script against sample labels
├── dataset/
│ ├── messages.csv # 110 incoming messages to route
│ ├── output.csv # Generated predictions (do not edit manually)
│ ├── sample_messages.csv # Solved examples with expected output
│ ├── users.csv # User notification behavior + quiet hours
│ ├── groups.csv # Group metadata (type, size, activity)
│ ├── group_members.csv # Per-user group role, mute state, activity
│ ├── business_accounts.csv # Business sender verification + reports
│ ├── user_business_history.csv# User–business relationship (orders, opt-ins)
│ ├── message_history.csv # Historical messages per user
│ ├── message_events.csv # User reactions to historical messages
│ ├── images.csv # Image IDs → file paths
│ ├── voice_notes.csv # Voice note IDs → file paths
│ ├── daily_notification_summary.csv
│ └── media/
│ ├── images/ # .jpg files referenced by images.csv
│ └── audio/ # .mp3 files referenced by voice_notes.csv
├── viewer/
│ ├── server/ # Express API — serves joined CSV data + media
│ │ └── index.js
│ └── client/ # Vite + React + Tailwind v4 simulation viewer
│ └── src/
│ ├── App.jsx
│ ├── main.jsx
│ └── index.css
├── .gitignore
├── AGENTS.md
├── problem_statement.md
└── README.md
| Tool | Version |
|---|---|
| Python | 3.9+ |
| Node.js | 18+ |
| npm | 9+ |
No API keys are required. The router uses only local CSV and media files.
cd code
python main.pyReads all files from dataset/ and writes dataset/output.csv with one prediction row per message. Prints the count on completion.
cd viewer
npm install
npm run devThis starts both the backend and frontend in the same terminal using concurrently:
| Process | URL |
|---|---|
| Express API | http://localhost:4000 |
| Vite frontend | http://localhost:5173 |
Open http://localhost:5173 in your browser.
The router is a deterministic rule-based classifier. It processes each message through a layered decision tree using all available context files:
1. Data loading
All CSV files are loaded once at startup into in-memory dictionaries keyed by their primary ID (user_id, group_id, business_id, etc.) for O(1) lookups per message.
2. Scam and safety check (highest priority)
Before any other logic, the message text is scanned for risk signals — OTP requests, credential pressure, suspicious URLs, account-blocking urgency, and prompt-injection attempts (messages that try to instruct the router itself). Any message hitting two or more risk keywords is immediately routed to mute / scam regardless of sender or conversation type.
3. Conversation-type branching
The pipeline then branches on conversation_type:
-
Business — checks
business_accounts.csvfor verification status anduser_business_history.csvfor an active relationship. Verified businesses with a matching order, booking, or appointment history are routednotify / business_update. Verified businesses without history default todigest. Unverified businesses with promotional language aremute / promotion. -
Group — checks
groups.csvfor group type andgroup_members.csvfor the user's mute state and role. High-context groups (society, coworker, school) with urgent keywords or trusted admin senders are routednotify / urgent. Family and extended-family groups with high forward counts or blessing/greeting patterns aremute / forwardordigest / greeting. Marketplace groups with selling language aredigest / promotion. Muted groups fall back todigest. -
Personal — re-runs the scam check first. Known trusted senders with urgent language are
notify / urgent. Unknown senders with no risk signals aredigest / personal.
4. Evidence retrieval
For each message, message_history.csv is searched for past messages to the same user with overlapping text tokens (three or more shared words, or substring match). Up to three matching historical message IDs are attached as evidence_message_ids. If no match is found, none is written.
5. Confidence scoring
Fixed confidence tiers are assigned by decision path:
0.90— scam/safety mute (high certainty)0.87— urgent group notify0.85— urgent personal notify0.82–0.84— verified business digest / marketplace0.78–0.80— non-urgent business or greeting digest0.74— muted group fallback0.72— ambiguous / unknown fallback
A MERN-stack web app for inspecting every input message alongside its routing decision side by side.
Backend (viewer/server/index.js) — Express server that:
- Reads
messages.csv,output.csv,groups.csv,users.csv, andbusiness_accounts.csvon each request usingcsv-parse - Joins all rows by their IDs into a single enriched object per message
- Serves media files (images and audio) directly from
dataset/media/via/api/media/image/:idand/api/media/audio/:id
Frontend (viewer/client/) — Vite + React 19 + Tailwind CSS v4:
- Fetches the joined simulation data from
/api/simulation - Renders each message as a collapsible card color-coded by action (green = notify, blue = digest, red = mute)
- Expanded view shows a two-column layout: left for raw input fields + message text / image / audio player, right for the routing decision with action badge, type badge, reason text, confidence bar, and evidence IDs
- Filter bar supports free-text search, action filter, and message-type filter
- Stats bar shows total / notify / digest / mute counts at a glance
Tailwind v4 setup uses @tailwindcss/vite as a first-class Vite plugin — no tailwind.config.js, no PostCSS config, just @import "tailwindcss" in index.css. This is the only correct setup for Tailwind v4 (CRA is incompatible).
dataset/output.csv columns in required order:
| Column | Type | Description |
|---|---|---|
message_id |
string | Matches input message_id |
action |
notify | digest | mute |
Routing decision |
message_type |
string | Best-fit category (see allowed values below) |
reason |
string | Human-readable explanation |
confidence |
float 0–1 | Certainty of the decision |
evidence_message_ids |
string | Semicolon-separated historical IDs, or none |
Allowed message_type values: personal, urgent, event, payment, business_update, promotion, greeting, forward, spam, scam, unknown
-
dataset/output.csvhas exactly 110 rows — one permessage_idinmessages.csv - All six required columns are present in the correct order
- No hardcoded labels or organizer-only files used
-
code/main.pyruns end-to-end from a clean terminal withpython main.py - Chat transcript log exists at
%USERPROFILE%\hackerrank_orchestrate_august26\log.txt