Spaces:
Running
Running
| import torch | |
| import os | |
| import re | |
| from transformers import DistilBertTokenizerFast, DistilBertForSequenceClassification | |
| # Changed: load from HuggingFace Hub instead of local path | |
| tokenizer = DistilBertTokenizerFast.from_pretrained("Shade63/sentinel-model") | |
| model = DistilBertForSequenceClassification.from_pretrained("Shade63/sentinel-model") | |
| model.eval() | |
| # Rule-based scam indicators (high confidence patterns) | |
| SCAM_SIGNAL_LABELS = [ | |
| ("arrest or legal authority threat", r'\b(arrest|warrant|fbi|cbi|irs|enforcement|legal action)\b'), | |
| ("urgency / time pressure", r'\b(urgent|immediate|now|today|within \d+ (hours?|minutes?))\b'), | |
| ("payment demand with urgency", r'\b(pay|payment|transfer|wire|funds?|money)\b.*\b(immediate|now|urgent)\b'), | |
| ("phishing link / click-bait", r'\b(click|verify|confirm).*\b(link|here|now)\b'), | |
| ("account suspension threat", r'\b(suspended|blocked|frozen|invalid).*\b(account|card|number)\b'), | |
| ("prize / lottery scam", r'\b(won|prize|lottery|claim)\b'), | |
| ("credential / remote access request", r'\b(otp|password|remote access|teamviewer|anydesk)\b'), | |
| ("digital arrest pattern", r'\b(digital arrest|stay on (the )?line)\b'), | |
| ("isolation / secrecy demand", r'\b(do not (disconnect|tell|go to))\b'), | |
| ("document / ID fraud", r'\b(aadhaar|sim|passport|visa).*\b(illegal|invalid|blocked)\b'), | |
| ] | |
| SAFE_KEYWORDS = [ | |
| r'\b(official public advisory|government agencies do not)\b', | |
| r'\b(legitimate authority|verifiable credentials)\b', | |
| r'\b(official portal|registered mail|written documentation)\b', | |
| r'\b(meeting|agenda|schedule|call|project)\b', | |
| r'\b(hello|hi|how are you)\b', | |
| ] | |
| def predict(text): | |
| text_lower = text.lower() | |
| # Rule-based scoring | |
| scam_score = 0 | |
| safe_score = 0 | |
| matched_signals = [] | |
| for label, pattern in SCAM_SIGNAL_LABELS: | |
| if re.search(pattern, text_lower, re.IGNORECASE): | |
| scam_score += 1 | |
| matched_signals.append(label.capitalize()) | |
| for pattern in SAFE_KEYWORDS: | |
| if re.search(pattern, text_lower, re.IGNORECASE): | |
| safe_score += 1 | |
| # If strong rule-based signal, use it | |
| if scam_score >= 2: | |
| scam_prob = min(0.85 + (scam_score * 0.05), 0.99) | |
| safe_prob = max(0.15 - (scam_score * 0.05), 0.01) | |
| return { | |
| "label": "SCAM", | |
| "scam_probability": round(scam_prob, 4), | |
| "safe_probability": round(safe_prob, 4), | |
| "risk_level": "HIGH", | |
| "signals": matched_signals, | |
| } | |
| elif safe_score >= 1 and scam_score == 0: | |
| return { | |
| "label": "SAFE", | |
| "scam_probability": 0.15, | |
| "safe_probability": 0.85, | |
| "risk_level": "LOW", | |
| "signals": [], | |
| } | |
| # Otherwise, use model prediction | |
| inputs = tokenizer(text, return_tensors="pt", truncation=True, padding=True) | |
| with torch.no_grad(): | |
| outputs = model(**inputs) | |
| logits = outputs.logits | |
| probabilities = torch.softmax(logits, dim=1) | |
| scam_prob = probabilities[0][1].item() | |
| safe_prob = probabilities[0][0].item() | |
| # Adjust probabilities based on rule scores | |
| if scam_score > 0: | |
| scam_prob = min(scam_prob + (scam_score * 0.1), 0.95) | |
| safe_prob = 1 - scam_prob | |
| elif safe_score > 0: | |
| safe_prob = min(safe_prob + (safe_score * 0.1), 0.95) | |
| scam_prob = 1 - safe_prob | |
| label = "SCAM" if scam_prob > 0.5 else "SAFE" | |
| if label == "SCAM": | |
| risk_level = "HIGH" if scam_prob >= 0.75 else "MEDIUM" | |
| else: | |
| risk_level = "LOW" | |
| return { | |
| "label": label, | |
| "scam_probability": round(scam_prob, 4), | |
| "safe_probability": round(safe_prob, 4), | |
| "risk_level": risk_level, | |
| "signals": matched_signals, | |
| } |