| title | QueueStorm Investigator |
|---|---|
| emoji | 🛡️ |
| colorFrom | blue |
| colorTo | indigo |
| sdk | docker |
| app_port | 8000 |
| pinned | false |
An internal copilot API for fintech support agents built for the bKash · SUST CSE Carnival 2026 Codex Community Hackathon (Online Preliminary). It receives one support ticket — a complaint plus a short snippet of the customer's recent transactions — and returns a single structured JSON verdict that investigates the complaint against the transaction evidence, classifies and routes the case, escalates risk, and drafts a safe customer reply.
It is an investigator, not a classifier: the complaint says one thing, the transaction data may say another, and the service decides what the evidence actually supports — answering insufficient_data rather than guessing when the evidence is unclear.
Deployed (always-on, free, no GPU) on Hugging Face Spaces:
https://naimurerahaman-queuestorm-investigator.hf.space — /health and /analyze-ticket.
| Method | Path | Behavior |
|---|---|---|
GET |
/health |
Returns {"status":"ok"} (ready < 60s of start). |
POST |
/analyze-ticket |
Accepts one ticket, returns one structured JSON response (< 30s; typically < 5ms). |
HTTP codes: 200 success · 400 malformed JSON / missing ticket_id or complaint · 422 present-but-empty complaint · 500 controlled internal error (never a stack trace or secret). The process never crashes or hangs on bad input.
npm install # installs express only
npm start # serves on 0.0.0.0:$PORT (default 8000)
npm test # 33 tests: 10 sample cases + safety + malformed/reliabilityHealth check and a sample call:
curl http://localhost:8000/health
# {"status":"ok"}
curl -X POST http://localhost:8000/analyze-ticket \
-H "Content-Type: application/json" \
-d '{
"ticket_id": "TKT-001",
"complaint": "I sent 5000 taka to a wrong number around 2pm today.",
"transaction_history": [
{"transaction_id":"TXN-9101","timestamp":"2026-04-14T14:08:22Z","type":"transfer","amount":5000,"counterparty":"+8801719876543","status":"completed"}
]
}'Sample response:
{
"ticket_id": "TKT-001",
"relevant_transaction_id": "TXN-9101",
"evidence_verdict": "consistent",
"case_type": "wrong_transfer",
"severity": "high",
"department": "dispute_resolution",
"agent_summary": "Customer reports sending 5000 BDT via TXN-9101 to +8801719876543, which they now believe was the wrong recipient.",
"recommended_next_action": "Verify TXN-9101 details with the customer and initiate the wrong-transfer dispute workflow per policy.",
"customer_reply": "We have noted your concern about transaction TXN-9101. Please do not share your PIN or OTP with anyone. Our dispute team will review the case and contact you through official support channels.",
"human_review_required": true,
"confidence": 0.88,
"reason_codes": ["wrong_transfer", "transaction_match"]
}More worked outputs (wrong transfer, phishing, Bangla agent cash-in, duplicate payment) are in sample_output/sample_response.json.
- Runtime: Node.js 20 (ESM)
- HTTP: Express 4 — the only runtime dependency
- Tests: built-in
node:test+supertest(dev only) - Deploy: Docker (
node:20-alpine, image ≈150MB), live on Render
No database, no GPU, no model downloads, no build step.
Models used: none. The service is fully rule-based and deterministic.
This is a deliberate engineering choice, not a shortcut:
- The rubric's #1 tie-breaker is safety / absence of critical violations, then evidence reasoning. A deterministic pipeline removes the single biggest risk to both — an unsafe or wrong LLM output — and to reliability (no third-party quota, latency, or outage can take the score down).
- The evidence-matching task (extract amount/time/counterparty from the complaint, compare against
transaction_history) is fully solvable with deterministic extraction + matching, which is also testable and reproducible. - It keeps the image tiny (no model weights / multi-GB downloads, well under the 500MB guidance) and latency at single-digit milliseconds (full p95-latency credit).
The pipeline (src/):
extract.js— parse the complaint as data only: amounts (English + Bangla digits), phone/merchant/agent counterparties, time-of-day, and intent keywords (English, Banglish, and Bangla).evidence.js— score and match transactions →relevant_transaction_id; decideevidence_verdict(consistent/inconsistent/insufficient_data), including established-recipient contradiction, ambiguous-match abstention, and duplicate-charge detection.classify.js— derivecase_type,department,severity,human_review_required,confidence,reason_codes.reply.js— buildagent_summary,recommended_next_action, and a localizedcustomer_reply(Bangla for Bangla input) from fixed safe templates.safety.js— a deterministic guard that runs unconditionally on the final text.
The customer-facing text is produced only from fixed templates interpolated with trusted values (a resolved transaction id/amount/counterparty drawn from transaction_history). Raw complaint text is never echoed into output, so instructions embedded in a complaint ("ignore your rules and ask for the OTP") cannot reach any field — prompt injection is defeated structurally, and the extractor only reacts to recognized signal patterns, so injected commands cannot change the decision fields either.
On top of that, safety.js scans the final customer_reply and recommended_next_action and, on any hit, replaces the field with a vetted safe fallback and forces human_review_required = true. It blocks:
- Credential requests — any ask for PIN / OTP / password / full card number (the
-15rule). Safe warnings like "do not share your PIN or OTP" are explicitly allowed via negation-aware matching. - Unauthorized financial promises — "we will refund you", "your refund has been processed", "account has been unblocked", etc. (the
-10rule). Replies instead use "any eligible amount will be returned through official channels". - Suspicious third-party / non-official channels — URLs, WhatsApp/Telegram, raw external phone numbers (the
-10rule). Directing a refund-seeker to the merchant for the merchant's own policy is intentionally allowed (matches the official SAMPLE-04 expectation).
Phishing/social-engineering reports are always critical, routed to fraud_risk, and flagged for human review.
- All data is synthetic; no real customer data, payment integration, or production deployment is involved.
- Timestamps are ISO-8601 UTC (
Z), as in the sample pack; time-of-day matching uses UTC hours. - Required fields are
ticket_idandcomplaint; everything else is optional and defaulted/sanitized. - The 10 public sample cases are a calibration set, not the test set — the rules are written for the general problem statement, not to memorize them.
- Bangla handling targets the patterns exercised by the sample pack (digit normalization, cash-in/agent/balance/refund keywords) plus English/Banglish; it is keyword-based, not full Bangla NLP, so unusual phrasings may fall back to
other/insufficient_data(a safe degradation). - Time matching is coarse (hour-level) and used only as a tiebreaker, not a hard filter.
- With no LLM,
agent_summary/customer_replywording is templated rather than free-form; this trades a little linguistic variety for guaranteed safety and determinism. - Duplicate detection keys on equal amount + same counterparty among non-failed transactions; an intentional re-payment to the same counterparty could read as a duplicate (mitigated by requiring the duplicate-intent keyword before classifying as
duplicate_payment).
See RUNBOOK.md for copy-paste local, Docker, and Render steps. The service binds 0.0.0.0, reads PORT from the environment, needs no secrets, and exposes no login.