Problem statement and target users

Fraud losses in the US alone hit $12.5 billion in 2023, with the highest per-incident losses among adults 60+. But people don't fall for scams because they don't know scams exist. They fall for them because every tactic - urgency, fake authority, secrecy - is engineered to cut you off from the person you'd normally ask. Rush you. Isolate you. Make asking for help feel embarrassing.

Existing defenses don't engage with this. Spam filters block or don't block, with no explanation. Public chatbots ask you to paste your bank SMS into someone else's server. Nothing in the consumer market manufactures a second opinion - the single most protective factor against a scam, and the exact thing every tactic is designed to prevent.

Target users, in priority order:

  1. Non-technical adults and older adults receiving scam SMS, calls, and emails - highest loss rates, lowest confidence in judging a message alone.
  2. Family members and caregivers who get the "is this real?" forward and have to judge it from a phone, often hours later.
  3. Small businesses facing business email compromise - invoice fraud, payroll redirection - where a wrong decision costs tens of thousands.

Technical approach and technical components

Design principles. Local-first (nothing leaves the user's machine). Grounded, not generated (every output derives from structured evidence). One code path (the app and the eval harness call the same src/fuse.py).

Five-stage pipeline:

  1. Extraction (no AI). Regex and heuristics pull URLs, phone numbers, amounts, brands, and keyword signals. Fully auditable.
  2. Three parallel analyzers:
    • Tactic classifier. DistilBERT fine-tuned for multi-label classification over 7 tactics (urgency, authority, financial lure, credential harvesting, payment redirection, secrecy, tech support). Trained on 14,600 ScamBench-derived examples plus 854 curated benign rows. Per-tactic decision thresholds tuned on validation.
    • URL feature engine. Lexical features plus Unicode confusable detection - catches homoglyph attacks, brand-in-subdomain tricks, suspicious TLDs, punycode, IP-literal URLs.
    • Pattern retrieval. Sentence-transformer embeddings + FAISS over 65 curated scam and legitimate patterns. Returns known cases as citable evidence.
  3. Fusion (src/fuse.py). Combines the three signals into a 0-100 risk score. Tactic and URL features are primary; retrieval and keyword signals are capped corroboration. Thresholds grid-searched across 36 combinations.
  4. Grounded explanation. Ollama qwen3:4b receives both the message and the structured evidence, and is constrained to describe only what's present. It cannot invent red flags.
  5. Guidance + trusted-contact brief. Action steps are template-based (deterministic). The brief is LLM-generated, prompted for a different audience - written for a family member, not the user.

Evaluation on a held-out 300-row balanced sample:

System Accuracy Precision Recall F1
Keyword rules 0.573 0.667 0.293 0.407
Zero-shot qwen3:4b 0.647 0.597 0.900 0.718
Second Look 0.920 0.871 0.987 0.925

Tactic-level: f1_micro 0.833, f1_macro 0.821. Matches the LLM's recall with 24 points more precision, at ~20x the throughput.

Stack. Python 3.11, PyTorch, Transformers, sentence-transformers, FAISS, Ollama, Streamlit, FastAPI.

Two false positives we found and fixed. A legit Amazon delivery scored 75/100 - traced to an out-of-distribution gap, fixed with 854 curated benign examples (val f1_macro 0.7610 → 0.7969). Then a doctor's appointment reminder scored 56/100 with a hallucinated explanation about a delivery link - traced to the LLM never seeing the original message and a too-permissive retrieval floor. Fixed without retraining. Both documented in the README.


How the solution creates real-world impact

Direct. Second Look changes what a suspicious message is. Instead of a private judgment made under pressure, it becomes a structured analysis with tactics named, evidence shown, and action steps ordered. The user learns which trick is being used - transferable to the next message, not just this one.

Indirect. The son in Toronto currently has two bad options: respond at 2 AM, or hope his mother waits. Second Look gives him a third - a short brief that tells him what was flagged, with no personal details from the original message, so he can weigh in in thirty seconds. It converts a high-stakes asynchronous judgment into a low-friction one.

Structural. Every serious tool in this space competes on detection accuracy - an arms race Google and Microsoft have already won. The behavioral gap is a different game, and it hasn't been productized. Moving the field from "did we catch the scam?" to "did we help the person decide?" applies to invoice fraud, payroll redirection, romance scams, and pig-butchering alike.

What we don't claim. No user study. No measured reduction in fraud loss. Decision support, not a legal or financial determination - the app says so on every screen, and tells users to verify through a channel they chose, not one from the message.

Why local-first matters specifically here. The users most vulnerable to scams are the least likely to trust a tool that asks for their bank SMS. Everything runs on the user's own device - classifier, embeddings, retrieval, LLM. The bank name in a phishing SMS never leaves the machine. That's what makes the tool usable by the people who need it most.

Built With

Share this project:

Updates

Submission history