Debdeep30's picture
Upload pipeline/scam_filter.py with huggingface_hub
7c5873c verified
Raw
History Blame Contribute Delete
4.07 kB
"""
Lightweight scam detection for Lumi.
Two-stage approach:
1. Keyword/pattern matching — fast, catches the obvious cases
2. Embedding similarity — catches paraphrased variants
If scam_probability >= THRESHOLD, returns a gentle deflection response instead
of forwarding to the LLM. The deflection never alarms the patient.
"""
from __future__ import annotations
import re
THRESHOLD = 0.7 # probability above which we deflect
# ---------------------------------------------------------------------------
# Stage 1 — keyword patterns
# ---------------------------------------------------------------------------
SCAM_PATTERNS = [
# urgent money requests
r"\b(send|transfer|wire|give|need)\b.{0,30}(money|cash|\$|pound|dollar)",
r"\burgent\b.{0,40}\b(payment|transfer|money|funds)",
# prize / lottery
r"\b(won|win|winner|lottery|prize|jackpot)\b",
r"\bcongratulations\b.{0,60}\b(prize|award|money|gift)",
# fake family emergency
r"\b(grandson|granddaughter|son|daughter|nephew|niece)\b.{0,40}\b(arrested|accident|hospital|trouble|jail|hurt)",
# bank / card details
r"\b(bank\s+account|account\s+number|sort\s+code|credit\s+card|debit\s+card|pin\b|password|social\s+security)",
# impersonation
r"\bthis\s+is\s+(your\s+bank|the\s+(police|irs|hmrc|government|medicare|social\s+security))",
# gift card scams
r"\b(gift\s+card|itunes|amazon\s+card|google\s+play)\b.{0,30}\b(buy|purchase|send|code)",
]
_compiled = [re.compile(p, re.IGNORECASE) for p in SCAM_PATTERNS]
def _keyword_score(text: str) -> float:
"""Returns a score 0-1 based on how many patterns match."""
hits = sum(1 for p in _compiled if p.search(text))
if hits >= 1:
return 1.0
return 0.0
# ---------------------------------------------------------------------------
# Stage 2 — embedding similarity (optional, loads lazily)
# ---------------------------------------------------------------------------
_embed_model = None
_scam_embeddings = None
SCAM_SEED_PHRASES = [
"Send me $500 immediately, I'm in trouble",
"You have won a lottery prize, claim now",
"Give me your bank account number",
"This is the IRS, you owe back taxes",
"Buy iTunes gift cards and send the codes",
"I'm your grandson and I had an accident",
"Your credit card has been compromised, verify details",
]
def _load_embeddings():
global _embed_model, _scam_embeddings
if _embed_model is None:
try:
from sentence_transformers import SentenceTransformer
import numpy as np
_embed_model = SentenceTransformer("all-MiniLM-L6-v2")
_scam_embeddings = _embed_model.encode(SCAM_SEED_PHRASES, normalize_embeddings=True)
except ImportError:
_embed_model = False # flag: not available
def _embedding_score(text: str) -> float:
_load_embeddings()
if not _embed_model:
return 0.0
import numpy as np
emb = _embed_model.encode([text], normalize_embeddings=True)
similarities = (_scam_embeddings @ emb.T).flatten()
return float(similarities.max())
# ---------------------------------------------------------------------------
# Public API
# ---------------------------------------------------------------------------
DEFLECTION_RESPONSE = (
"That sounds like something we should check with your family first. "
"Let me make a note for them. "
"You don't need to do anything right now — you're completely safe."
)
def scam_probability(text: str) -> float:
kw = _keyword_score(text)
em = _embedding_score(text)
return max(kw, em * 0.8) # keyword takes precedence; embedding adds coverage
def check_and_deflect(user_text: str) -> tuple[bool, str]:
"""
Returns (is_scam, response).
If is_scam is True, response is the safe deflection message.
If is_scam is False, response is empty — proceed normally to the LLM.
"""
prob = scam_probability(user_text)
if prob >= THRESHOLD:
return True, DEFLECTION_RESPONSE
return False, ""