Sanjam19 commited on
Commit
cfe63e2
·
verified ·
1 Parent(s): c4618fd

Upload 12 files

Browse files
Files changed (12) hide show
  1. README.md +82 -13
  2. app.py +67 -0
  3. database.py +14 -0
  4. evaluate.py +64 -0
  5. gitignore +6 -0
  6. main.py +337 -0
  7. ml_utils.py +1128 -0
  8. models.py +39 -0
  9. movies2.pkl +3 -0
  10. requirements.txt +6 -0
  11. similarity2.pkl +3 -0
  12. test.py +51 -0
README.md CHANGED
@@ -1,13 +1,82 @@
1
- ---
2
- title: Scam Detector
3
- emoji: 🏃
4
- colorFrom: indigo
5
- colorTo: green
6
- sdk: gradio
7
- sdk_version: 6.9.0
8
- app_file: app.py
9
- pinned: false
10
- short_description: Scam Detector with 98.5% accuracy
11
- ---
12
-
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Movie Recommender System
2
+
3
+ A simple movie recommendation system built using **content-based filtering** to suggest similar movies based on user selection.
4
+
5
+ ## About This Project
6
+
7
+ This is a **learning project** I built to understand basic Machine Learning concepts like:
8
+ - **K-Nearest Neighbors (KNN)** for finding similar items
9
+ - **Cosine Similarity** for measuring movie similarity
10
+ - **Feature Engineering** and text vectorization
11
+ - Working with real-world datasets
12
+
13
+ ## Dataset
14
+
15
+ - **TMDB 5000 Movie Dataset** (from Kaggle)
16
+ - Contains movie metadata like genres, cast, crew, keywords, overview, etc.
17
+
18
+ ## Tech Stack
19
+
20
+ - **Python** - Core programming language
21
+ - **Streamlit** - Web interface
22
+ - **Pandas** - Data manipulation
23
+ - **Scikit-learn** - ML algorithms (CountVectorizer, Cosine Similarity)
24
+ - **Pickle** - Model serialization
25
+ - **TMDB API** - Fetching movie posters
26
+
27
+ ## How It Works
28
+
29
+ 1. **Data Preprocessing:** Combined multiple features (genres, keywords, cast, crew) into a single text field
30
+ 2. **Vectorization:** Converted text data into numerical vectors using CountVectorizer
31
+ 3. **Similarity Calculation:** Used cosine similarity to find movies with similar feature vectors
32
+ 4. **Recommendation:** Returns top 5 most similar movies based on the selected movie
33
+
34
+ ## Installation & Setup
35
+
36
+ ```bash
37
+ # Clone the repository
38
+ git clone https://github.com/SD1920/Movie_Recommender.git
39
+ cd Movie_Recommender
40
+
41
+ # Install dependencies
42
+ pip install -r requirements.txt
43
+
44
+ # Run the app
45
+ streamlit run app.py
46
+ ```
47
+
48
+ ## Project Structure
49
+
50
+ ```
51
+ Movie_Recommender/
52
+ │
53
+ ├── app.py # Main Streamlit application
54
+ ├── requirements.txt # Python dependencies
55
+ ├── .gitignore
56
+ └── README.md
57
+ ```
58
+
59
+ > **Note:** The `.pkl` files (movies2.pkl, similarity2.pkl) are automatically downloaded from Google Drive when you run the app for the first time.
60
+
61
+ ## Features
62
+
63
+ - Search and select from 5000+ movies
64
+ - Get 5 similar movie recommendations
65
+ - View movie posters fetched from TMDB API
66
+ - Clean and simple UI
67
+
68
+ ## Learning Outcomes
69
+
70
+ Through this project, I learned:
71
+ - How recommendation systems work
72
+ - Text preprocessing and feature extraction
73
+ - Similarity metrics in ML
74
+ - Building web apps with Streamlit
75
+ - Working with APIs
76
+
77
+ ## Future Improvements
78
+
79
+ - Add collaborative filtering
80
+ - Implement hybrid recommendation (content + collaborative)
81
+ - Add movie ratings and reviews
82
+ - Improve UI/UX
app.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from ml_utils import ScamDetectionService
3
+
4
+ # Initialize model
5
+ detector = ScamDetectionService()
6
+
7
+ def analyze_text(text):
8
+ result = detector.analyze_text_scam(text)
9
+ return f"""
10
+ **Risk Level:** {result['risk_level']}
11
+ **Confidence:** {result['confidence']:.1%}
12
+ **Reasoning:** {result['reasoning']}
13
+ **Language:** {result['detected_language']}
14
+ """
15
+
16
+ def analyze_url(url, context):
17
+ result = detector.analyze_url_scam(url, context)
18
+ return f"""
19
+ **Risk Level:** {result['risk_level']}
20
+ **Confidence:** {result['confidence']:.1%}
21
+ **Reasoning:** {result['reasoning']}
22
+ **Domain:** {result['domain']}
23
+ """
24
+
25
+ # Create Gradio interface
26
+ with gr.Blocks(title="Scam Detector") as demo:
27
+ gr.Markdown("# 🛡️ Scam Detector - 98.5% Accuracy")
28
+ gr.Markdown("AI-powered spam & phishing detection system")
29
+
30
+ with gr.Tab("Text Analysis"):
31
+ text_input = gr.Textbox(
32
+ label="Enter message to analyze",
33
+ placeholder="CONGRATULATIONS! You've WON £1000!",
34
+ lines=3
35
+ )
36
+ text_button = gr.Button("Analyze Text")
37
+ text_output = gr.Markdown()
38
+ text_button.click(analyze_text, inputs=text_input, outputs=text_output)
39
+
40
+ with gr.Tab("URL Analysis"):
41
+ url_input = gr.Textbox(
42
+ label="Enter URL",
43
+ placeholder="http://paypa1-secure.tk/verify"
44
+ )
45
+ context_input = gr.Textbox(
46
+ label="Message context (optional)",
47
+ placeholder="Your account has been suspended...",
48
+ lines=2
49
+ )
50
+ url_button = gr.Button("Analyze URL")
51
+ url_output = gr.Markdown()
52
+ url_button.click(
53
+ analyze_url,
54
+ inputs=[url_input, context_input],
55
+ outputs=url_output
56
+ )
57
+
58
+ gr.Markdown("""
59
+ ### How it works:
60
+ - **Text Analysis**: Detects spam/phishing in SMS/emails using TF-IDF + Logistic Regression
61
+ - **URL Analysis**: Checks for phishing domains, typosquatting, suspicious TLDs
62
+ - **Accuracy**: 98.5% on SMS Spam Collection dataset (5,574 messages)
63
+ - **Performance**: 100% recall, 97.1% precision
64
+ """)
65
+
66
+ if __name__ == "__main__":
67
+ demo.launch()
database.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # database.py
2
+
3
+ from sqlalchemy import create_engine
4
+ from sqlalchemy.orm import sessionmaker
5
+ import os
6
+
7
+ DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./scam_detection.db")
8
+
9
+ engine = create_engine(
10
+ DATABASE_URL,
11
+ connect_args={"check_same_thread": False} if "sqlite" in DATABASE_URL else {}
12
+ )
13
+
14
+ SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
evaluate.py ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # evaluate.py
2
+ # Run this ONCE to generate metrics for your resume.
3
+ # Dataset: SMS Spam Collection — download from:
4
+ # https://www.kaggle.com/datasets/uciml/sms-spam-collection-dataset
5
+ # Save as spam.csv in the same directory before running.
6
+
7
+ import pandas as pd
8
+ from sklearn.metrics import (
9
+ accuracy_score, precision_score,
10
+ recall_score, f1_score, classification_report
11
+ )
12
+ from ml_utils import ScamDetectionService
13
+
14
+ def run_evaluation(csv_path: str = "spam.csv", sample_size: int = 200):
15
+ """
16
+ Evaluates the scam detection model on the SMS Spam Collection dataset.
17
+ Uses a sample to keep runtime reasonable (zero-shot is slow on CPU).
18
+
19
+ Args:
20
+ csv_path: path to the downloaded spam.csv
21
+ sample_size: how many rows to evaluate (200 is enough for metrics)
22
+ """
23
+
24
+ print("Loading dataset...")
25
+ df = pd.read_csv(csv_path, encoding='latin-1')[['v1', 'v2']]
26
+ df.columns = ['label', 'text']
27
+
28
+ # Map ham/spam -> Safe/Scam
29
+ df['true_label'] = df['label'].map({'ham': 'Safe', 'spam': 'Scam'})
30
+
31
+ # Sample evenly across both classes
32
+ sample_df = df.groupby('true_label', group_keys=False).apply(
33
+ lambda x: x.sample(min(len(x), sample_size // 2), random_state=42)
34
+ ).reset_index(drop=True)
35
+
36
+ print(f"Evaluating on {len(sample_df)} samples...")
37
+
38
+ service = ScamDetectionService()
39
+
40
+ predictions = []
41
+ for i, row in sample_df.iterrows():
42
+ result = service.analyze_text_scam(row['text'])
43
+ # Collapse Suspicious -> Scam for binary eval
44
+ pred = "Scam" if result["risk_level"] in ["Scam", "Suspicious"] else "Safe"
45
+ predictions.append(pred)
46
+
47
+ if (i + 1) % 20 == 0:
48
+ print(f" Progress: {i + 1}/{len(sample_df)}")
49
+
50
+ true_labels = sample_df['true_label'].tolist()
51
+
52
+ print("\n========== EVALUATION RESULTS ==========")
53
+ print(f"Accuracy : {accuracy_score(true_labels, predictions):.4f}")
54
+ print(f"Precision : {precision_score(true_labels, predictions, pos_label='Scam'):.4f}")
55
+ print(f"Recall : {recall_score(true_labels, predictions, pos_label='Scam'):.4f}")
56
+ print(f"F1 Score : {f1_score(true_labels, predictions, pos_label='Scam'):.4f}")
57
+ print("\nFull Report:")
58
+ print(classification_report(true_labels, predictions))
59
+ print("=========================================")
60
+ print("\nCopy these numbers into your README and resume.")
61
+
62
+
63
+ if __name__ == "__main__":
64
+ run_evaluation()
gitignore ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ ml/
2
+ *.pkl
3
+ __pycache__/
4
+ .idea/
5
+ movies2.pkl
6
+ similarity2.pkl
main.py ADDED
@@ -0,0 +1,337 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #main.py
2
+ from fastapi import FastAPI, HTTPException, Depends, status
3
+ from fastapi.middleware.cors import CORSMiddleware
4
+ from pydantic import BaseModel, Field
5
+ from sqlalchemy.orm import Session
6
+ import models
7
+ from database import engine, SessionLocal
8
+ from ml_utils import ScamDetectionService
9
+ from typing import Optional
10
+ import time
11
+ from datetime import datetime
12
+
13
+ # Create FastAPI app
14
+ app = FastAPI(
15
+ title="Multilingual Scam Detection API",
16
+ description="Simple API to detect scams in text messages and URLs",
17
+ version="1.0.0"
18
+ )
19
+
20
+ # Add CORS middleware
21
+ # app.add_middleware(
22
+ # CORSMiddleware,
23
+ # allow_origins=["*"], # Configure for production
24
+ # allow_credentials=True,
25
+ # allow_methods=["*"],
26
+ # allow_headers=["*"],
27
+ # )
28
+
29
+ # Create database tables
30
+ models.Base.metadata.create_all(bind=engine)
31
+
32
+ # Initialize scam detection service
33
+ scam_service = ScamDetectionService()
34
+
35
+
36
+ # Database dependency
37
+ def get_db():
38
+ db = SessionLocal()
39
+ try:
40
+ yield db
41
+ finally:
42
+ db.close()
43
+
44
+
45
+ # Pydantic models for requests/responses
46
+ class TextAnalysisRequest(BaseModel):
47
+ text: str = Field(min_length=1, max_length=5000)
48
+ language: Optional[str] = Field(default=None, description="Language code (en, es, fr)")
49
+
50
+
51
+ class URLAnalysisRequest(BaseModel):
52
+ url: str = Field(min_length=1)
53
+ context: Optional[str] = Field(default="", description="Additional context")
54
+
55
+
56
+ class ScamAnalysisResponse(BaseModel):
57
+ risk_level: str
58
+ confidence: float
59
+ reasoning: str
60
+ detected_language: Optional[str] = None
61
+ processing_time: float
62
+ timestamp: datetime
63
+
64
+
65
+ class URLAnalysisResponse(ScamAnalysisResponse):
66
+ url_status: str
67
+ domain: str
68
+
69
+
70
+ # Root endpoint
71
+ @app.get("/")
72
+ def root():
73
+ return {
74
+ "message": "Multilingual Scam Detection API",
75
+ "version": "1.0.0",
76
+ "endpoints": {
77
+ "analyze_text": "/analyze/text/",
78
+ "analyze_url": "/analyze/url/",
79
+ "health": "/health/",
80
+ "analytics": "/analytics/summary/"
81
+ }
82
+ }
83
+
84
+
85
+ # Health check
86
+ @app.get("/health/")
87
+ def health_check():
88
+ return {
89
+ "status": "healthy",
90
+ "timestamp": datetime.now(),
91
+ "database": "connected",
92
+ "ml_service": "active"
93
+ }
94
+
95
+
96
+ # Text analysis endpoint
97
+ @app.post("/analyze/text/", response_model=ScamAnalysisResponse)
98
+ def analyze_text(request: TextAnalysisRequest, db: Session = Depends(get_db)):
99
+ """Analyze text message for scam indicators"""
100
+ start_time = time.time()
101
+
102
+ try:
103
+ # Detect language if not provided
104
+ detected_language = request.language or scam_service.detect_language(request.text)
105
+
106
+ # Analyze for scam
107
+ analysis = scam_service.analyze_text_scam(
108
+ text=request.text,
109
+ language=detected_language
110
+ )
111
+
112
+ processing_time = time.time() - start_time
113
+
114
+ # Save to database
115
+ scam_record = models.ScamAnalysis(
116
+ content=request.text,
117
+ content_type="text",
118
+ risk_level=analysis["risk_level"],
119
+ confidence=analysis["confidence"],
120
+ reasoning=analysis["reasoning"],
121
+ detected_language=detected_language,
122
+ processing_time=processing_time
123
+ )
124
+
125
+ db.add(scam_record)
126
+ db.commit()
127
+
128
+ return ScamAnalysisResponse(
129
+ risk_level=analysis["risk_level"],
130
+ confidence=analysis["confidence"],
131
+ reasoning=analysis["reasoning"],
132
+ detected_language=detected_language,
133
+ processing_time=round(processing_time, 3),
134
+ timestamp=datetime.now()
135
+ )
136
+
137
+ except Exception as e:
138
+ raise HTTPException(
139
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
140
+ detail=f"Analysis failed: {str(e)}"
141
+ )
142
+
143
+
144
+ # URL analysis endpoint
145
+ @app.post("/analyze/url/", response_model=URLAnalysisResponse)
146
+ def analyze_url(request: URLAnalysisRequest, db: Session = Depends(get_db)):
147
+ """Analyze URL for phishing/malicious behavior"""
148
+ start_time = time.time()
149
+
150
+ try:
151
+ # Analyze URL
152
+ analysis = scam_service.analyze_url_scam(
153
+ url=request.url,
154
+ context=request.context
155
+ )
156
+
157
+ processing_time = time.time() - start_time
158
+
159
+ # Save to database
160
+ scam_record = models.ScamAnalysis(
161
+ content=request.url,
162
+ content_type="url",
163
+ risk_level=analysis["risk_level"],
164
+ confidence=analysis["confidence"],
165
+ reasoning=analysis["reasoning"],
166
+ processing_time=processing_time,
167
+ additional_data={"domain": analysis["domain"], "url_status": analysis["url_status"]}
168
+ )
169
+
170
+ db.add(scam_record)
171
+ db.commit()
172
+
173
+ return URLAnalysisResponse(
174
+ risk_level=analysis["risk_level"],
175
+ confidence=analysis["confidence"],
176
+ reasoning=analysis["reasoning"],
177
+ url_status=analysis["url_status"],
178
+ domain=analysis["domain"],
179
+ processing_time=round(processing_time, 3),
180
+ timestamp=datetime.now()
181
+ )
182
+
183
+ except Exception as e:
184
+ raise HTTPException(
185
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
186
+ detail=f"URL analysis failed: {str(e)}"
187
+ )
188
+
189
+
190
+ # Language detection endpoint
191
+ @app.post("/detect/language/")
192
+ def detect_language(text: str):
193
+ """Detect language of input text"""
194
+ try:
195
+ detected_lang = scam_service.detect_language(text)
196
+ confidence = scam_service.get_language_confidence(text)
197
+
198
+ return {
199
+ "text_preview": text[:100] + "..." if len(text) > 100 else text,
200
+ "detected_language": detected_lang,
201
+ "confidence": confidence,
202
+ "supported_languages": ["en", "es", "fr"]
203
+ }
204
+
205
+ except Exception as e:
206
+ raise HTTPException(
207
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
208
+ detail=f"Language detection failed: {str(e)}"
209
+ )
210
+
211
+
212
+ # Basic analytics
213
+ @app.get("/analytics/summary/")
214
+ def get_analytics(db: Session = Depends(get_db)):
215
+ """Get basic analytics summary"""
216
+ try:
217
+ total_analyses = db.query(models.ScamAnalysis).count()
218
+
219
+ # Count by risk level
220
+ safe_count = db.query(models.ScamAnalysis).filter(
221
+ models.ScamAnalysis.risk_level == "Safe"
222
+ ).count()
223
+
224
+ suspicious_count = db.query(models.ScamAnalysis).filter(
225
+ models.ScamAnalysis.risk_level == "Suspicious"
226
+ ).count()
227
+
228
+ scam_count = db.query(models.ScamAnalysis).filter(
229
+ models.ScamAnalysis.risk_level == "Scam"
230
+ ).count()
231
+
232
+ # Count by content type
233
+ text_count = db.query(models.ScamAnalysis).filter(
234
+ models.ScamAnalysis.content_type == "text"
235
+ ).count()
236
+
237
+ url_count = db.query(models.ScamAnalysis).filter(
238
+ models.ScamAnalysis.content_type == "url"
239
+ ).count()
240
+
241
+ return {
242
+ "total_analyses": total_analyses,
243
+ "risk_distribution": {
244
+ "Safe": safe_count,
245
+ "Suspicious": suspicious_count,
246
+ "Scam": scam_count
247
+ },
248
+ "content_distribution": {
249
+ "text": text_count,
250
+ "url": url_count
251
+ },
252
+ "supported_languages": ["en", "es", "fr"]
253
+ }
254
+
255
+ except Exception as e:
256
+ raise HTTPException(
257
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
258
+ detail=f"Analytics failed: {str(e)}"
259
+ )
260
+
261
+
262
+ # Recent analyses
263
+ @app.get("/history/recent/")
264
+ def get_recent_analyses(limit: int = 10, db: Session = Depends(get_db)):
265
+ """Get recent scam analyses"""
266
+ try:
267
+ recent = db.query(models.ScamAnalysis).order_by(
268
+ models.ScamAnalysis.created_at.desc()
269
+ ).limit(limit).all()
270
+
271
+ return {
272
+ "recent_analyses": [
273
+ {
274
+ "id": analysis.id,
275
+ "content_type": analysis.content_type,
276
+ "risk_level": analysis.risk_level,
277
+ "confidence": analysis.confidence,
278
+ "detected_language": analysis.detected_language,
279
+ "created_at": analysis.created_at,
280
+ "content_preview": (
281
+ analysis.content[:50] + "..."
282
+ if len(analysis.content) > 50
283
+ else analysis.content
284
+ )
285
+ }
286
+ for analysis in recent
287
+ ],
288
+ "total_shown": len(recent)
289
+ }
290
+
291
+ except Exception as e:
292
+ raise HTTPException(
293
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
294
+ detail=f"History retrieval failed: {str(e)}"
295
+ )
296
+
297
+
298
+ # Test endpoint with sample data
299
+ @app.get("/test/samples/")
300
+ def test_samples():
301
+ """Test with sample scam messages"""
302
+ samples = [
303
+ {
304
+ "text": "URGENT! Your bank account will be suspended. Click here: bit.ly/urgent-bank",
305
+ "expected_risk": "Scam"
306
+ },
307
+ {
308
+ "text": "Hello, how are you today? Hope you're doing well!",
309
+ "expected_risk": "Safe"
310
+ },
311
+ {
312
+ "text": "Congratulations! You've won $1000. Click to claim your prize now!",
313
+ "expected_risk": "Suspicious"
314
+ }
315
+ ]
316
+
317
+ results = []
318
+ for sample in samples:
319
+ analysis = scam_service.analyze_text_scam(sample["text"])
320
+ results.append({
321
+ "text": sample["text"],
322
+ "expected": sample["expected_risk"],
323
+ "detected": analysis["risk_level"],
324
+ "confidence": analysis["confidence"],
325
+ "reasoning": analysis["reasoning"]
326
+ })
327
+
328
+ return {
329
+ "test_results": results,
330
+ "total_tests": len(results)
331
+ }
332
+
333
+
334
+ if __name__ == "__main__":
335
+ import uvicorn
336
+
337
+ uvicorn.run(app, host="0.0.0.0", port=8000)
ml_utils.py ADDED
@@ -0,0 +1,1128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # # ml_utils.py
2
+
3
+ # import re
4
+ # import logging
5
+ # from typing import Dict, Optional
6
+ # from urllib.parse import urlparse
7
+
8
+ # from transformers import pipeline
9
+ # from langdetect import detect, LangDetectError
10
+
11
+ # logging.basicConfig(level=logging.INFO)
12
+ # logger = logging.getLogger(__name__)
13
+
14
+
15
+ # class ScamDetectionService:
16
+ # def __init__(self):
17
+ # logger.info("Loading zero-shot classification model...")
18
+
19
+ # # Real HuggingFace zero-shot classifier
20
+ # # facebook/bart-large-mnli is free, no fine-tuning needed, works out of the box
21
+ # self.classifier = pipeline(
22
+ # "zero-shot-classification",
23
+ # model="facebook/bart-large-mnli"
24
+ # )
25
+
26
+ # self.scam_labels = ["scam or phishing", "suspicious", "safe"]
27
+
28
+ # # URL shorteners — rule-based check on top of ML
29
+ # self.suspicious_domains = [
30
+ # 'bit.ly', 'tinyurl.com', 'short.link', 'tiny.cc', 'ow.ly'
31
+ # ]
32
+
33
+ # logger.info("Model loaded successfully.")
34
+
35
+ # def detect_language(self, text: str) -> str:
36
+ # """Detect language using langdetect library"""
37
+ # try:
38
+ # return detect(text)
39
+ # except LangDetectError:
40
+ # return 'en' # default to English if detection fails
41
+
42
+ # def get_language_confidence(self, text: str) -> float:
43
+ # """Rough confidence based on text length"""
44
+ # word_count = len(text.split())
45
+ # if word_count < 5:
46
+ # return 0.6
47
+ # elif word_count < 15:
48
+ # return 0.8
49
+ # return 0.9
50
+
51
+ # def analyze_text_scam(self, text: str, language: str = None) -> Dict:
52
+ # """Analyze text using zero-shot NLP classification"""
53
+ # if not text or not text.strip():
54
+ # return {
55
+ # "risk_level": "Safe",
56
+ # "confidence": 0.0,
57
+ # "reasoning": "Empty text provided",
58
+ # "detected_language": "unknown"
59
+ # }
60
+
61
+ # detected_language = language or self.detect_language(text)
62
+
63
+ # try:
64
+ # result = self.classifier(text, candidate_labels=self.scam_labels)
65
+
66
+ # # result["labels"][0] is the top predicted label
67
+ # top_label = result["labels"][0]
68
+ # top_score = result["scores"][0]
69
+
70
+ # # Map model output to our risk levels
71
+ # if "scam" in top_label:
72
+ # risk_level = "Scam"
73
+ # elif "suspicious" in top_label:
74
+ # risk_level = "Suspicious"
75
+ # else:
76
+ # risk_level = "Safe"
77
+
78
+ # reasoning = f"Model classified as '{top_label}' with {round(top_score * 100, 1)}% confidence"
79
+
80
+ # return {
81
+ # "risk_level": risk_level,
82
+ # "confidence": round(top_score, 4),
83
+ # "reasoning": reasoning,
84
+ # "detected_language": detected_language
85
+ # }
86
+
87
+ # except Exception as e:
88
+ # logger.error(f"Classification failed: {e}")
89
+ # return {
90
+ # "risk_level": "Suspicious",
91
+ # "confidence": 0.5,
92
+ # "reasoning": f"Model error, defaulting to suspicious: {str(e)}",
93
+ # "detected_language": detected_language
94
+ # }
95
+
96
+ # def analyze_url_scam(self, url: str, context: str = "") -> Dict:
97
+ # """
98
+ # Analyze URL using a combination of:
99
+ # 1. Rule-based checks (IP, known patterns, shorteners)
100
+ # 2. Zero-shot classification on the URL string + context
101
+ # """
102
+ # if not url:
103
+ # return {
104
+ # "risk_level": "Safe",
105
+ # "confidence": 0.0,
106
+ # "reasoning": "No URL provided",
107
+ # "domain": "",
108
+ # "url_status": "invalid"
109
+ # }
110
+
111
+ # try:
112
+ # if not url.startswith(('http://', 'https://')):
113
+ # url = 'http://' + url
114
+
115
+ # parsed_url = urlparse(url)
116
+ # domain = parsed_url.netloc.lower()
117
+
118
+ # rule_risk = 0.0
119
+ # rule_reasons = []
120
+
121
+ # known_scam_patterns = [
122
+ # "faceb00k", "paypa1", "amaz0n", "micros0ft",
123
+ # "appleid-support", "login-secure", "claim-your-free",
124
+ # "lottery", "techsupport", "quickloan"
125
+ # ]
126
+
127
+ # if any(pattern in domain for pattern in known_scam_patterns):
128
+ # rule_risk += 0.7
129
+ # rule_reasons.append("Matches known scam domain pattern")
130
+
131
+ # if any(s in domain for s in self.suspicious_domains):
132
+ # rule_risk += 0.6
133
+ # rule_reasons.append("Uses URL shortening service")
134
+
135
+ # if re.search(r'\d+\.\d+\.\d+\.\d+', domain):
136
+ # rule_risk += 0.8
137
+ # rule_reasons.append("Uses raw IP address instead of domain")
138
+
139
+ # if len(url) > 100:
140
+ # rule_risk += 0.2
141
+ # rule_reasons.append("Unusually long URL")
142
+
143
+ # suspicious_words = ['login', 'verify', 'update', 'confirm', 'secure']
144
+ # if any(word in url.lower() for word in suspicious_words):
145
+ # rule_risk += 0.3
146
+ # rule_reasons.append("Contains suspicious keywords in URL")
147
+
148
+ # # Also run zero-shot on the URL + context string for extra signal
149
+ # analysis_text = f"URL: {url}"
150
+ # if context:
151
+ # analysis_text += f" Context: {context}"
152
+
153
+ # ml_result = self.classifier(analysis_text, candidate_labels=self.scam_labels)
154
+ # ml_top_label = ml_result["labels"][0]
155
+ # ml_top_score = ml_result["scores"][0]
156
+
157
+ # # Blend rule-based + ML score
158
+ # if "scam" in ml_top_label:
159
+ # ml_risk = ml_top_score
160
+ # elif "suspicious" in ml_top_label:
161
+ # ml_risk = ml_top_score * 0.5
162
+ # else:
163
+ # ml_risk = 0.0
164
+
165
+ # final_score = min((rule_risk * 0.6) + (ml_risk * 0.4), 1.0)
166
+
167
+ # if final_score >= 0.6:
168
+ # risk_level = "Scam"
169
+ # elif final_score >= 0.35:
170
+ # risk_level = "Suspicious"
171
+ # else:
172
+ # risk_level = "Safe"
173
+
174
+ # rule_summary = "; ".join(rule_reasons) if rule_reasons else "No rule-based flags"
175
+ # reasoning = f"{rule_summary} | ML: '{ml_top_label}' ({round(ml_top_score * 100, 1)}%)"
176
+
177
+ # return {
178
+ # "risk_level": risk_level,
179
+ # "confidence": round(final_score, 4),
180
+ # "reasoning": reasoning,
181
+ # "domain": domain,
182
+ # "url_status": "analyzed"
183
+ # }
184
+
185
+ # except Exception as e:
186
+ # return {
187
+ # "risk_level": "Suspicious",
188
+ # "confidence": 0.5,
189
+ # "reasoning": f"URL analysis error: {str(e)}",
190
+ # "domain": "unknown",
191
+ # "url_status": "error"
192
+ # }
193
+
194
+ # def generate_user_response(self, risk_level: str) -> str:
195
+ # responses = {
196
+ # "Safe": "✅ This message appears safe.",
197
+ # "Suspicious": "⚠️ Be cautious - this message has suspicious elements.",
198
+ # "Scam": "🚨 WARNING: This appears to be a scam! Do not click links or share personal info."
199
+ # }
200
+ # return responses.get(risk_level, "Unable to analyze.")
201
+
202
+
203
+ ##############################################################################################
204
+ # ml_utils.py
205
+
206
+ # import re
207
+ # import logging
208
+ # from typing import Dict, Optional
209
+ # from urllib.parse import urlparse
210
+
211
+ # from transformers import pipeline
212
+ # from langdetect import detect
213
+ # from langdetect.lang_detect_exception import LangDetectException
214
+
215
+ # logging.basicConfig(level=logging.INFO)
216
+ # logger = logging.getLogger(__name__)
217
+
218
+
219
+ # class ScamDetectionService:
220
+ # def __init__(self):
221
+ # logger.info("Loading zero-shot classification model...")
222
+
223
+ # # Real HuggingFace zero-shot classifier
224
+ # # facebook/bart-large-mnli is free, no fine-tuning needed, works out of the box
225
+ # self.classifier = pipeline(
226
+ # "zero-shot-classification",
227
+ # model="facebook/bart-large-mnli"
228
+ # )
229
+
230
+ # self.scam_labels = ["scam or phishing", "suspicious", "safe"]
231
+
232
+ # # URL shorteners — rule-based check on top of ML
233
+ # self.suspicious_domains = [
234
+ # 'bit.ly', 'tinyurl.com', 'short.link', 'tiny.cc', 'ow.ly'
235
+ # ]
236
+
237
+ # logger.info("Model loaded successfully.")
238
+
239
+ # def detect_language(self, text: str) -> str:
240
+ # """Detect language using langdetect library"""
241
+ # try:
242
+ # return detect(text)
243
+ # except LangDetectException:
244
+ # return 'en' # default to English if detection fails
245
+
246
+ # def get_language_confidence(self, text: str) -> float:
247
+ # """Rough confidence based on text length"""
248
+ # word_count = len(text.split())
249
+ # if word_count < 5:
250
+ # return 0.6
251
+ # elif word_count < 15:
252
+ # return 0.8
253
+ # return 0.9
254
+
255
+ # def analyze_text_scam(self, text: str, language: str = None) -> Dict:
256
+ # """Analyze text using zero-shot NLP classification"""
257
+ # if not text or not text.strip():
258
+ # return {
259
+ # "risk_level": "Safe",
260
+ # "confidence": 0.0,
261
+ # "reasoning": "Empty text provided",
262
+ # "detected_language": "unknown"
263
+ # }
264
+
265
+ # detected_language = language or self.detect_language(text)
266
+
267
+ # try:
268
+ # result = self.classifier(text, candidate_labels=self.scam_labels)
269
+
270
+ # # result["labels"][0] is the top predicted label
271
+ # top_label = result["labels"][0]
272
+ # top_score = result["scores"][0]
273
+
274
+ # # Map model output to our risk levels
275
+ # if "scam" in top_label:
276
+ # risk_level = "Scam"
277
+ # elif "suspicious" in top_label:
278
+ # risk_level = "Suspicious"
279
+ # else:
280
+ # risk_level = "Safe"
281
+
282
+ # reasoning = f"Model classified as '{top_label}' with {round(top_score * 100, 1)}% confidence"
283
+
284
+ # return {
285
+ # "risk_level": risk_level,
286
+ # "confidence": round(top_score, 4),
287
+ # "reasoning": reasoning,
288
+ # "detected_language": detected_language
289
+ # }
290
+
291
+ # except Exception as e:
292
+ # logger.error(f"Classification failed: {e}")
293
+ # return {
294
+ # "risk_level": "Suspicious",
295
+ # "confidence": 0.5,
296
+ # "reasoning": f"Model error, defaulting to suspicious: {str(e)}",
297
+ # "detected_language": detected_language
298
+ # }
299
+
300
+ # def analyze_url_scam(self, url: str, context: str = "") -> Dict:
301
+ # """
302
+ # Analyze URL using a combination of:
303
+ # 1. Rule-based checks (IP, known patterns, shorteners)
304
+ # 2. Zero-shot classification on the URL string + context
305
+ # """
306
+ # if not url:
307
+ # return {
308
+ # "risk_level": "Safe",
309
+ # "confidence": 0.0,
310
+ # "reasoning": "No URL provided",
311
+ # "domain": "",
312
+ # "url_status": "invalid"
313
+ # }
314
+
315
+ # try:
316
+ # if not url.startswith(('http://', 'https://')):
317
+ # url = 'http://' + url
318
+
319
+ # parsed_url = urlparse(url)
320
+ # domain = parsed_url.netloc.lower()
321
+
322
+ # rule_risk = 0.0
323
+ # rule_reasons = []
324
+
325
+ # known_scam_patterns = [
326
+ # "faceb00k", "paypa1", "amaz0n", "micros0ft",
327
+ # "appleid-support", "login-secure", "claim-your-free",
328
+ # "lottery", "techsupport", "quickloan"
329
+ # ]
330
+
331
+ # if any(pattern in domain for pattern in known_scam_patterns):
332
+ # rule_risk += 0.7
333
+ # rule_reasons.append("Matches known scam domain pattern")
334
+
335
+ # if any(s in domain for s in self.suspicious_domains):
336
+ # rule_risk += 0.6
337
+ # rule_reasons.append("Uses URL shortening service")
338
+
339
+ # if re.search(r'\d+\.\d+\.\d+\.\d+', domain):
340
+ # rule_risk += 0.8
341
+ # rule_reasons.append("Uses raw IP address instead of domain")
342
+
343
+ # if len(url) > 100:
344
+ # rule_risk += 0.2
345
+ # rule_reasons.append("Unusually long URL")
346
+
347
+ # suspicious_words = ['login', 'verify', 'update', 'confirm', 'secure']
348
+ # if any(word in url.lower() for word in suspicious_words):
349
+ # rule_risk += 0.3
350
+ # rule_reasons.append("Contains suspicious keywords in URL")
351
+
352
+ # # Also run zero-shot on the URL + context string for extra signal
353
+ # analysis_text = f"URL: {url}"
354
+ # if context:
355
+ # analysis_text += f" Context: {context}"
356
+
357
+ # ml_result = self.classifier(analysis_text, candidate_labels=self.scam_labels)
358
+ # ml_top_label = ml_result["labels"][0]
359
+ # ml_top_score = ml_result["scores"][0]
360
+
361
+ # # Blend rule-based + ML score
362
+ # if "scam" in ml_top_label:
363
+ # ml_risk = ml_top_score
364
+ # elif "suspicious" in ml_top_label:
365
+ # ml_risk = ml_top_score * 0.5
366
+ # else:
367
+ # ml_risk = 0.0
368
+
369
+ # final_score = min((rule_risk * 0.6) + (ml_risk * 0.4), 1.0)
370
+
371
+ # if final_score >= 0.6:
372
+ # risk_level = "Scam"
373
+ # elif final_score >= 0.35:
374
+ # risk_level = "Suspicious"
375
+ # else:
376
+ # risk_level = "Safe"
377
+
378
+ # rule_summary = "; ".join(rule_reasons) if rule_reasons else "No rule-based flags"
379
+ # reasoning = f"{rule_summary} | ML: '{ml_top_label}' ({round(ml_top_score * 100, 1)}%)"
380
+
381
+ # return {
382
+ # "risk_level": risk_level,
383
+ # "confidence": round(final_score, 4),
384
+ # "reasoning": reasoning,
385
+ # "domain": domain,
386
+ # "url_status": "analyzed"
387
+ # }
388
+
389
+ # except Exception as e:
390
+ # return {
391
+ # "risk_level": "Suspicious",
392
+ # "confidence": 0.5,
393
+ # "reasoning": f"URL analysis error: {str(e)}",
394
+ # "domain": "unknown",
395
+ # "url_status": "error"
396
+ # }
397
+
398
+ # def generate_user_response(self, risk_level: str) -> str:
399
+ # responses = {
400
+ # "Safe": "✅ This message appears safe.",
401
+ # "Suspicious": "⚠️ Be cautious - this message has suspicious elements.",
402
+ # "Scam": "🚨 WARNING: This appears to be a scam! Do not click links or share personal info."
403
+ # }
404
+ # return responses.get(risk_level, "Unable to analyze.")
405
+
406
+ #################################################################################################
407
+
408
+ # ml_utils.py - FIXED VERSION
409
+
410
+ # import re
411
+ # import logging
412
+ # from typing import Dict
413
+ # from urllib.parse import urlparse
414
+
415
+ # # Use a REAL spam detection model
416
+ # from transformers import pipeline
417
+
418
+ # logging.basicConfig(level=logging.INFO)
419
+ # logger = logging.getLogger(__name__)
420
+
421
+
422
+ # class ScamDetectionService:
423
+ # def __init__(self):
424
+ # logger.info("Loading spam detection model...")
425
+
426
+ # # Option A: mrm8488/bert-tiny-finetuned-sms-spam-detection
427
+ # # 90%+ accuracy on SMS spam, only 17MB
428
+ # self.classifier = pipeline(
429
+ # "text-classification",
430
+ # model="mrm8488/bert-tiny-finetuned-sms-spam-detection"
431
+ # )
432
+
433
+ # # URL shortener blacklist
434
+ # self.suspicious_domains = [
435
+ # 'bit.ly', 'tinyurl.com', 'short.link', 'tiny.cc', 'ow.ly'
436
+ # ]
437
+
438
+ # logger.info("Model loaded successfully.")
439
+
440
+ # def detect_language(self, text: str) -> str:
441
+ # """Simple language detection based on character set"""
442
+ # # Lightweight approach - no need for langdetect
443
+ # if re.search(r'[\u0900-\u097F]', text): # Devanagari
444
+ # return 'hi'
445
+ # elif re.search(r'[\u0980-\u09FF]', text): # Bengali/Odia
446
+ # return 'or'
447
+ # else:
448
+ # return 'en'
449
+
450
+ # def analyze_text_scam(self, text: str, language: str = None) -> Dict:
451
+ # """Analyze text using fine-tuned spam detection model"""
452
+ # if not text or not text.strip():
453
+ # return {
454
+ # "risk_level": "Safe",
455
+ # "confidence": 0.0,
456
+ # "reasoning": "Empty text provided",
457
+ # "detected_language": "unknown"
458
+ # }
459
+
460
+ # detected_language = language or self.detect_language(text)
461
+
462
+ # try:
463
+ # # Get prediction
464
+ # result = self.classifier(text)[0]
465
+
466
+ # # Model returns: {'label': 'SPAM', 'score': 0.99}
467
+ # label = result['label'].upper()
468
+ # score = result['score']
469
+
470
+ # # Map to your risk levels
471
+ # if label == 'SPAM' and score > 0.7:
472
+ # risk_level = "Scam"
473
+ # confidence = score
474
+ # elif label == 'SPAM' and score > 0.4:
475
+ # risk_level = "Suspicious"
476
+ # confidence = score * 0.8
477
+ # else:
478
+ # risk_level = "Safe"
479
+ # confidence = 1 - score
480
+
481
+ # # Add rule-based boost for common scam patterns
482
+ # scam_keywords = [
483
+ # 'congratulations', 'winner', 'claim', 'urgent', 'verify',
484
+ # 'account suspended', 'prize', 'free money', 'click here',
485
+ # 'act now', 'limited time', 'bank details'
486
+ # ]
487
+
488
+ # keyword_count = sum(1 for kw in scam_keywords if kw.lower() in text.lower())
489
+ # if keyword_count >= 2:
490
+ # # Boost confidence if multiple scam keywords present
491
+ # confidence = min(confidence + 0.15, 0.99)
492
+ # if risk_level == "Safe":
493
+ # risk_level = "Suspicious"
494
+
495
+ # reasoning = f"Spam classifier: {label} ({round(score * 100, 1)}%)"
496
+ # if keyword_count > 0:
497
+ # reasoning += f" + {keyword_count} scam keywords detected"
498
+
499
+ # return {
500
+ # "risk_level": risk_level,
501
+ # "confidence": round(confidence, 4),
502
+ # "reasoning": reasoning,
503
+ # "detected_language": detected_language
504
+ # }
505
+
506
+ # except Exception as e:
507
+ # logger.error(f"Classification failed: {e}")
508
+ # return {
509
+ # "risk_level": "Suspicious",
510
+ # "confidence": 0.5,
511
+ # "reasoning": f"Model error: {str(e)}",
512
+ # "detected_language": detected_language
513
+ # }
514
+
515
+ # def analyze_url_scam(self, url: str, context: str = "") -> Dict:
516
+ # """
517
+ # Analyze URL using rule-based checks + ML on context
518
+ # """
519
+ # if not url:
520
+ # return {
521
+ # "risk_level": "Safe",
522
+ # "confidence": 0.0,
523
+ # "reasoning": "No URL provided",
524
+ # "domain": "",
525
+ # "url_status": "invalid"
526
+ # }
527
+
528
+ # try:
529
+ # if not url.startswith(('http://', 'https://')):
530
+ # url = 'http://' + url
531
+
532
+ # parsed_url = urlparse(url)
533
+ # domain = parsed_url.netloc.lower()
534
+
535
+ # rule_risk = 0.0
536
+ # rule_reasons = []
537
+
538
+ # # Known scam domain patterns
539
+ # scam_patterns = [
540
+ # "faceb00k", "paypa1", "amaz0n", "micros0ft",
541
+ # "appleid-support", "login-secure", "claim-your-free",
542
+ # "lottery", "techsupport", "quickloan", "verify-account"
543
+ # ]
544
+
545
+ # if any(pattern in domain for pattern in scam_patterns):
546
+ # rule_risk += 0.8
547
+ # rule_reasons.append("Domain mimics trusted brand")
548
+
549
+ # if any(s in domain for s in self.suspicious_domains):
550
+ # rule_risk += 0.6
551
+ # rule_reasons.append("URL shortening service")
552
+
553
+ # # IP address instead of domain
554
+ # if re.search(r'\d+\.\d+\.\d+\.\d+', domain):
555
+ # rule_risk += 0.9
556
+ # rule_reasons.append("Uses raw IP address")
557
+
558
+ # # Suspicious TLDs
559
+ # suspicious_tlds = ['.tk', '.ml', '.ga', '.cf', '.gq', '.xyz']
560
+ # if any(domain.endswith(tld) for tld in suspicious_tlds):
561
+ # rule_risk += 0.5
562
+ # rule_reasons.append("Suspicious top-level domain")
563
+
564
+ # # Long URL (often phishing)
565
+ # if len(url) > 100:
566
+ # rule_risk += 0.3
567
+ # rule_reasons.append("Unusually long URL")
568
+
569
+ # # Security-related words in URL
570
+ # security_words = ['login', 'verify', 'update', 'confirm', 'secure', 'account']
571
+ # if any(word in url.lower() for word in security_words):
572
+ # rule_risk += 0.4
573
+ # rule_reasons.append("Contains security-related keywords")
574
+
575
+ # # If context provided, analyze it for spam
576
+ # ml_risk = 0.0
577
+ # if context:
578
+ # context_result = self.analyze_text_scam(context)
579
+ # if context_result['risk_level'] == 'Scam':
580
+ # ml_risk = context_result['confidence']
581
+ # elif context_result['risk_level'] == 'Suspicious':
582
+ # ml_risk = context_result['confidence'] * 0.5
583
+
584
+ # # Combine rule-based + ML
585
+ # final_score = min((rule_risk * 0.7) + (ml_risk * 0.3), 1.0)
586
+
587
+ # if final_score >= 0.7:
588
+ # risk_level = "Scam"
589
+ # elif final_score >= 0.4:
590
+ # risk_level = "Suspicious"
591
+ # else:
592
+ # risk_level = "Safe"
593
+
594
+ # rule_summary = "; ".join(rule_reasons) if rule_reasons else "No suspicious patterns"
595
+ # reasoning = f"{rule_summary}"
596
+ # if context:
597
+ # reasoning += f" | Context analysis: {context_result['risk_level']}"
598
+
599
+ # return {
600
+ # "risk_level": risk_level,
601
+ # "confidence": round(final_score, 4),
602
+ # "reasoning": reasoning,
603
+ # "domain": domain,
604
+ # "url_status": "analyzed"
605
+ # }
606
+
607
+ # except Exception as e:
608
+ # return {
609
+ # "risk_level": "Suspicious",
610
+ # "confidence": 0.5,
611
+ # "reasoning": f"URL analysis error: {str(e)}",
612
+ # "domain": "unknown",
613
+ # "url_status": "error"
614
+ # }
615
+
616
+ # def generate_user_response(self, risk_level: str) -> str:
617
+ # responses = {
618
+ # "Safe": "✅ This message appears safe.",
619
+ # "Suspicious": "⚠️ Be cautious - this message has suspicious elements.",
620
+ # "Scam": "🚨 WARNING: This appears to be a scam! Do not click links or share personal info."
621
+ # }
622
+ # return responses.get(risk_level, "Unable to analyze.")
623
+ ###################################################################################################
624
+
625
+ # ml_utils.py - PROPERLY CALIBRATED VERSION
626
+
627
+ # import re
628
+ # import logging
629
+ # from typing import Dict
630
+ # from urllib.parse import urlparse
631
+
632
+ # from transformers import pipeline
633
+
634
+ # logging.basicConfig(level=logging.INFO)
635
+ # logger = logging.getLogger(__name__)
636
+
637
+
638
+ # class ScamDetectionService:
639
+ # def __init__(self):
640
+ # logger.info("Loading spam detection model...")
641
+
642
+ # self.classifier = pipeline(
643
+ # "text-classification",
644
+ # model="mrm8488/bert-tiny-finetuned-sms-spam-detection"
645
+ # )
646
+
647
+ # self.suspicious_domains = [
648
+ # 'bit.ly', 'tinyurl.com', 'short.link', 'tiny.cc', 'ow.ly'
649
+ # ]
650
+
651
+ # logger.info("Model loaded successfully.")
652
+
653
+ # def detect_language(self, text: str) -> str:
654
+ # """Simple language detection based on character set"""
655
+ # if re.search(r'[\u0900-\u097F]', text):
656
+ # return 'hi'
657
+ # elif re.search(r'[\u0980-\u09FF]', text):
658
+ # return 'or'
659
+ # else:
660
+ # return 'en'
661
+
662
+ # def analyze_text_scam(self, text: str, language: str = None) -> Dict:
663
+ # """Analyze text using fine-tuned spam detection model"""
664
+ # if not text or not text.strip():
665
+ # return {
666
+ # "risk_level": "Safe",
667
+ # "confidence": 0.0,
668
+ # "reasoning": "Empty text provided",
669
+ # "detected_language": "unknown"
670
+ # }
671
+
672
+ # detected_language = language or self.detect_language(text)
673
+
674
+ # try:
675
+ # result = self.classifier(text)[0]
676
+
677
+ # label = result['label'].upper()
678
+ # score = result['score']
679
+
680
+ # # FIXED: Proper threshold mapping
681
+ # if label == 'SPAM':
682
+ # # Model says SPAM
683
+ # if score >= 0.5: # Changed from 0.7 - trust the model more
684
+ # risk_level = "Scam"
685
+ # confidence = score
686
+ # else:
687
+ # risk_level = "Suspicious"
688
+ # confidence = score * 0.9
689
+ # else:
690
+ # # Model says HAM (not spam)
691
+ # if score >= 0.8: # Very confident it's safe
692
+ # risk_level = "Safe"
693
+ # confidence = score
694
+ # elif score >= 0.5: # Somewhat confident it's safe
695
+ # risk_level = "Safe"
696
+ # confidence = score * 0.8
697
+ # else:
698
+ # # Model unsure, check for keywords
699
+ # risk_level = "Suspicious"
700
+ # confidence = 1 - score
701
+
702
+ # # Rule-based keyword boost
703
+ # scam_keywords = [
704
+ # 'congratulations', 'winner', 'won', 'claim', 'urgent',
705
+ # 'verify', 'suspended', 'prize', 'free money', 'click here',
706
+ # 'act now', 'limited time', 'expire', 'confirm', 'update',
707
+ # 'dear customer', 'bank details', 'credit card', 'password'
708
+ # ]
709
+
710
+ # text_lower = text.lower()
711
+ # keyword_matches = [kw for kw in scam_keywords if kw in text_lower]
712
+ # keyword_count = len(keyword_matches)
713
+
714
+ # # Boost scam score if keywords present
715
+ # if keyword_count >= 3:
716
+ # if risk_level == "Safe":
717
+ # risk_level = "Scam"
718
+ # confidence = min(confidence + 0.3, 0.95)
719
+ # elif risk_level == "Suspicious":
720
+ # risk_level = "Scam"
721
+ # confidence = min(confidence + 0.2, 0.90)
722
+ # elif keyword_count >= 2:
723
+ # if risk_level == "Safe":
724
+ # risk_level = "Suspicious"
725
+ # confidence = min(confidence + 0.2, 0.85)
726
+
727
+ # # Check for common spam patterns
728
+ # spam_patterns = [
729
+ # r'\b(click here|click now)\b',
730
+ # r'\b(act now|hurry|urgent)\b',
731
+ # r'\b(won|winner|prize)\b',
732
+ # r'\b(free money|cash prize)\b',
733
+ # r'\b(verify|confirm|update).*(account|password|details)\b',
734
+ # r'\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b', # Credit card pattern
735
+ # ]
736
+
737
+ # pattern_matches = sum(1 for pattern in spam_patterns if re.search(pattern, text_lower))
738
+
739
+ # if pattern_matches >= 2 and risk_level != "Scam":
740
+ # risk_level = "Scam"
741
+ # confidence = min(confidence + 0.25, 0.92)
742
+
743
+ # reasoning = f"ML: {label} ({round(score * 100, 1)}%)"
744
+ # if keyword_count > 0:
745
+ # reasoning += f" | {keyword_count} scam keywords: {', '.join(keyword_matches[:3])}"
746
+ # if pattern_matches > 0:
747
+ # reasoning += f" | {pattern_matches} suspicious patterns"
748
+
749
+ # return {
750
+ # "risk_level": risk_level,
751
+ # "confidence": round(confidence, 4),
752
+ # "reasoning": reasoning,
753
+ # "detected_language": detected_language
754
+ # }
755
+
756
+ # except Exception as e:
757
+ # logger.error(f"Classification failed: {e}")
758
+ # return {
759
+ # "risk_level": "Suspicious",
760
+ # "confidence": 0.5,
761
+ # "reasoning": f"Model error: {str(e)}",
762
+ # "detected_language": detected_language
763
+ # }
764
+
765
+ # def analyze_url_scam(self, url: str, context: str = "") -> Dict:
766
+ # """Analyze URL using rule-based checks + ML on context"""
767
+ # if not url:
768
+ # return {
769
+ # "risk_level": "Safe",
770
+ # "confidence": 0.0,
771
+ # "reasoning": "No URL provided",
772
+ # "domain": "",
773
+ # "url_status": "invalid"
774
+ # }
775
+
776
+ # try:
777
+ # if not url.startswith(('http://', 'https://')):
778
+ # url = 'http://' + url
779
+
780
+ # parsed_url = urlparse(url)
781
+ # domain = parsed_url.netloc.lower()
782
+
783
+ # rule_risk = 0.0
784
+ # rule_reasons = []
785
+
786
+ # # Known scam patterns
787
+ # scam_patterns = [
788
+ # "faceb00k", "paypa1", "amaz0n", "micros0ft", "g00gle",
789
+ # "appleid", "login-secure", "claim-your", "verify-account",
790
+ # "lottery", "techsupport", "quickloan", "account-update"
791
+ # ]
792
+
793
+ # if any(pattern in domain for pattern in scam_patterns):
794
+ # rule_risk += 0.8
795
+ # rule_reasons.append("Domain mimics trusted brand")
796
+
797
+ # if any(s in domain for s in self.suspicious_domains):
798
+ # rule_risk += 0.6
799
+ # rule_reasons.append("URL shortening service")
800
+
801
+ # if re.search(r'\d+\.\d+\.\d+\.\d+', domain):
802
+ # rule_risk += 0.9
803
+ # rule_reasons.append("Uses raw IP address")
804
+
805
+ # suspicious_tlds = ['.tk', '.ml', '.ga', '.cf', '.gq', '.xyz', '.top']
806
+ # if any(domain.endswith(tld) for tld in suspicious_tlds):
807
+ # rule_risk += 0.7
808
+ # rule_reasons.append("Suspicious top-level domain")
809
+
810
+ # if len(url) > 100:
811
+ # rule_risk += 0.3
812
+ # rule_reasons.append("Unusually long URL")
813
+
814
+ # security_words = ['login', 'verify', 'update', 'confirm', 'secure', 'account']
815
+ # if any(word in url.lower() for word in security_words):
816
+ # rule_risk += 0.4
817
+ # rule_reasons.append("Security-related keywords in URL")
818
+
819
+ # # Analyze context if provided
820
+ # ml_risk = 0.0
821
+ # if context:
822
+ # context_result = self.analyze_text_scam(context)
823
+ # if context_result['risk_level'] == 'Scam':
824
+ # ml_risk = context_result['confidence']
825
+ # elif context_result['risk_level'] == 'Suspicious':
826
+ # ml_risk = context_result['confidence'] * 0.6
827
+
828
+ # final_score = min((rule_risk * 0.65) + (ml_risk * 0.35), 1.0)
829
+
830
+ # if final_score >= 0.65:
831
+ # risk_level = "Scam"
832
+ # elif final_score >= 0.35:
833
+ # risk_level = "Suspicious"
834
+ # else:
835
+ # risk_level = "Safe"
836
+
837
+ # rule_summary = "; ".join(rule_reasons) if rule_reasons else "No suspicious patterns"
838
+ # reasoning = f"{rule_summary}"
839
+ # if context:
840
+ # reasoning += f" | Message context: {context_result['risk_level']}"
841
+
842
+ # return {
843
+ # "risk_level": risk_level,
844
+ # "confidence": round(final_score, 4),
845
+ # "reasoning": reasoning,
846
+ # "domain": domain,
847
+ # "url_status": "analyzed"
848
+ # }
849
+
850
+ # except Exception as e:
851
+ # return {
852
+ # "risk_level": "Suspicious",
853
+ # "confidence": 0.5,
854
+ # "reasoning": f"URL analysis error: {str(e)}",
855
+ # "domain": "unknown",
856
+ # "url_status": "error"
857
+ # }
858
+
859
+ # def generate_user_response(self, risk_level: str) -> str:
860
+ # responses = {
861
+ # "Safe": "✅ This message appears safe.",
862
+ # "Suspicious": "⚠️ Be cautious - this message has suspicious elements.",
863
+ # "Scam": "🚨 WARNING: This appears to be a scam! Do not click links or share personal info."
864
+ # }
865
+ # return responses.get(risk_level, "Unable to analyze.")
866
+
867
+ ##################################################################################################
868
+
869
+ # ml_utils.py - TF-IDF + Logistic Regression (PROVEN APPROACH)
870
+
871
+ import re
872
+ import pickle
873
+ import logging
874
+ from typing import Dict
875
+ from urllib.parse import urlparse
876
+ from pathlib import Path
877
+
878
+ from sklearn.feature_extraction.text import TfidfVectorizer
879
+ from sklearn.linear_model import LogisticRegression
880
+ from sklearn.pipeline import Pipeline
881
+ import pandas as pd
882
+
883
+ logging.basicConfig(level=logging.INFO)
884
+ logger = logging.getLogger(__name__)
885
+
886
+
887
+ class ScamDetectionService:
888
+ def __init__(self):
889
+ logger.info("Loading spam detection model...")
890
+
891
+ model_path = Path("spam_model.pkl")
892
+
893
+ if model_path.exists():
894
+ with open(model_path, 'rb') as f:
895
+ self.model = pickle.load(f)
896
+ logger.info("Model loaded from disk.")
897
+ else:
898
+ logger.info("Training new model on SMS Spam dataset...")
899
+ self.train_model()
900
+
901
+ self.suspicious_domains = [
902
+ 'bit.ly', 'tinyurl.com', 'short.link', 'tiny.cc', 'ow.ly'
903
+ ]
904
+
905
+ logger.info("Model ready.")
906
+
907
+ def train_model(self):
908
+ """Train TF-IDF + Logistic Regression on spam dataset"""
909
+ try:
910
+ df = pd.read_csv("spam.csv", encoding='latin-1')[['v1', 'v2']]
911
+ df.columns = ['label', 'text']
912
+
913
+ # Convert labels: ham -> 0, spam -> 1
914
+ df['label'] = df['label'].map({'ham': 0, 'spam': 1})
915
+
916
+ # Build pipeline
917
+ self.model = Pipeline([
918
+ ('tfidf', TfidfVectorizer(
919
+ max_features=3000,
920
+ ngram_range=(1, 2), # Unigrams + bigrams
921
+ stop_words='english',
922
+ min_df=2,
923
+ sublinear_tf=True
924
+ )),
925
+ ('clf', LogisticRegression(
926
+ max_iter=1000,
927
+ C=1.0,
928
+ class_weight='balanced' # Handle class imbalance
929
+ ))
930
+ ])
931
+
932
+ # Train on full dataset
933
+ logger.info(f"Training on {len(df)} messages...")
934
+ self.model.fit(df['text'], df['label'])
935
+
936
+ # Save model
937
+ with open("spam_model.pkl", 'wb') as f:
938
+ pickle.dump(self.model, f)
939
+
940
+ logger.info("Model trained and saved to spam_model.pkl")
941
+
942
+ except FileNotFoundError:
943
+ logger.error("spam.csv not found! Please download the SMS Spam Collection dataset.")
944
+ raise
945
+ except Exception as e:
946
+ logger.error(f"Training failed: {e}")
947
+ raise
948
+
949
+ def detect_language(self, text: str) -> str:
950
+ """Simple language detection based on character set"""
951
+ if re.search(r'[\u0900-\u097F]', text): # Devanagari
952
+ return 'hi'
953
+ elif re.search(r'[\u0980-\u09FF]', text): # Bengali/Odia
954
+ return 'or'
955
+ else:
956
+ return 'en'
957
+
958
+ def analyze_text_scam(self, text: str, language: str = None) -> Dict:
959
+ """Analyze text using trained spam classifier"""
960
+ if not text or not text.strip():
961
+ return {
962
+ "risk_level": "Safe",
963
+ "confidence": 0.0,
964
+ "reasoning": "Empty text provided",
965
+ "detected_language": "unknown"
966
+ }
967
+
968
+ detected_language = language or self.detect_language(text)
969
+
970
+ try:
971
+ # Get probability scores [ham_prob, spam_prob]
972
+ proba = self.model.predict_proba([text])[0]
973
+ spam_prob = proba[1] # Probability of spam class
974
+
975
+ # Map to risk levels with adjusted thresholds
976
+ if spam_prob >= 0.65:
977
+ risk_level = "Scam"
978
+ confidence = spam_prob
979
+ elif spam_prob >= 0.35:
980
+ risk_level = "Suspicious"
981
+ confidence = spam_prob
982
+ else:
983
+ risk_level = "Safe"
984
+ confidence = 1 - spam_prob
985
+
986
+ # Additional rule-based checks for edge cases
987
+ scam_keywords = [
988
+ 'congratulations', 'winner', 'won', 'claim', 'urgent',
989
+ 'verify', 'suspended', 'prize', 'free money', 'click here',
990
+ 'act now', 'limited time', 'expire', 'confirm'
991
+ ]
992
+
993
+ text_lower = text.lower()
994
+ keyword_count = sum(1 for kw in scam_keywords if kw in text_lower)
995
+
996
+ # Boost if multiple scam keywords present
997
+ if keyword_count >= 3 and risk_level == "Safe":
998
+ risk_level = "Suspicious"
999
+ confidence = max(confidence, 0.6)
1000
+
1001
+ reasoning = f"Spam probability: {round(spam_prob * 100, 1)}%"
1002
+ if keyword_count > 0:
1003
+ reasoning += f" ({keyword_count} scam keywords detected)"
1004
+
1005
+ return {
1006
+ "risk_level": risk_level,
1007
+ "confidence": round(confidence, 4),
1008
+ "reasoning": reasoning,
1009
+ "detected_language": detected_language
1010
+ }
1011
+
1012
+ except Exception as e:
1013
+ logger.error(f"Classification failed: {e}")
1014
+ return {
1015
+ "risk_level": "Suspicious",
1016
+ "confidence": 0.5,
1017
+ "reasoning": f"Model error: {str(e)}",
1018
+ "detected_language": detected_language
1019
+ }
1020
+
1021
+ def analyze_url_scam(self, url: str, context: str = "") -> Dict:
1022
+ """
1023
+ Analyze URL using rule-based checks + ML on context
1024
+ """
1025
+ if not url:
1026
+ return {
1027
+ "risk_level": "Safe",
1028
+ "confidence": 0.0,
1029
+ "reasoning": "No URL provided",
1030
+ "domain": "",
1031
+ "url_status": "invalid"
1032
+ }
1033
+
1034
+ try:
1035
+ if not url.startswith(('http://', 'https://')):
1036
+ url = 'http://' + url
1037
+
1038
+ parsed_url = urlparse(url)
1039
+ domain = parsed_url.netloc.lower()
1040
+
1041
+ rule_risk = 0.0
1042
+ rule_reasons = []
1043
+
1044
+ # Known scam domain patterns
1045
+ scam_patterns = [
1046
+ "faceb00k", "paypa1", "amaz0n", "micros0ft", "g00gle",
1047
+ "appleid", "login-secure", "claim-your", "verify-account",
1048
+ "lottery", "techsupport", "quickloan", "account-update"
1049
+ ]
1050
+
1051
+ if any(pattern in domain for pattern in scam_patterns):
1052
+ rule_risk += 0.8
1053
+ rule_reasons.append("Domain mimics trusted brand")
1054
+
1055
+ if any(s in domain for s in self.suspicious_domains):
1056
+ rule_risk += 0.6
1057
+ rule_reasons.append("URL shortening service")
1058
+
1059
+ # IP address instead of domain
1060
+ if re.search(r'\d+\.\d+\.\d+\.\d+', domain):
1061
+ rule_risk += 0.9
1062
+ rule_reasons.append("Uses raw IP address")
1063
+
1064
+ # Suspicious TLDs
1065
+ suspicious_tlds = ['.tk', '.ml', '.ga', '.cf', '.gq', '.xyz', '.top']
1066
+ if any(domain.endswith(tld) for tld in suspicious_tlds):
1067
+ rule_risk += 0.7
1068
+ rule_reasons.append("Suspicious top-level domain")
1069
+
1070
+ # Long URL (often phishing)
1071
+ if len(url) > 100:
1072
+ rule_risk += 0.3
1073
+ rule_reasons.append("Unusually long URL")
1074
+
1075
+ # Security-related words in URL
1076
+ security_words = ['login', 'verify', 'update', 'confirm', 'secure', 'account']
1077
+ if any(word in url.lower() for word in security_words):
1078
+ rule_risk += 0.4
1079
+ rule_reasons.append("Security-related keywords in URL")
1080
+
1081
+ # Analyze context if provided
1082
+ ml_risk = 0.0
1083
+ if context:
1084
+ context_result = self.analyze_text_scam(context)
1085
+ if context_result['risk_level'] == 'Scam':
1086
+ ml_risk = context_result['confidence']
1087
+ elif context_result['risk_level'] == 'Suspicious':
1088
+ ml_risk = context_result['confidence'] * 0.6
1089
+
1090
+ # Combine rule-based + ML
1091
+ final_score = min((rule_risk * 0.65) + (ml_risk * 0.35), 1.0)
1092
+
1093
+ if final_score >= 0.65:
1094
+ risk_level = "Scam"
1095
+ elif final_score >= 0.35:
1096
+ risk_level = "Suspicious"
1097
+ else:
1098
+ risk_level = "Safe"
1099
+
1100
+ rule_summary = "; ".join(rule_reasons) if rule_reasons else "No suspicious patterns"
1101
+ reasoning = f"{rule_summary}"
1102
+ if context:
1103
+ reasoning += f" | Message context: {context_result['risk_level']}"
1104
+
1105
+ return {
1106
+ "risk_level": risk_level,
1107
+ "confidence": round(final_score, 4),
1108
+ "reasoning": reasoning,
1109
+ "domain": domain,
1110
+ "url_status": "analyzed"
1111
+ }
1112
+
1113
+ except Exception as e:
1114
+ return {
1115
+ "risk_level": "Suspicious",
1116
+ "confidence": 0.5,
1117
+ "reasoning": f"URL analysis error: {str(e)}",
1118
+ "domain": "unknown",
1119
+ "url_status": "error"
1120
+ }
1121
+
1122
+ def generate_user_response(self, risk_level: str) -> str:
1123
+ responses = {
1124
+ "Safe": "✅ This message appears safe.",
1125
+ "Suspicious": "⚠️ Be cautious - this message has suspicious elements.",
1126
+ "Scam": "🚨 WARNING: This appears to be a scam! Do not click links or share personal info."
1127
+ }
1128
+ return responses.get(risk_level, "Unable to analyze.")
models.py ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #models.py
2
+
3
+ from sqlalchemy import Column, Integer, String, Float, DateTime, Text, JSON
4
+ from sqlalchemy.ext.declarative import declarative_base
5
+ from sqlalchemy.sql import func
6
+
7
+ Base = declarative_base()
8
+
9
+
10
+ class ScamAnalysis(Base):
11
+ __tablename__ = "scam_analyses"
12
+
13
+ id = Column(Integer, primary_key=True, index=True)
14
+ content = Column(Text, nullable=False)
15
+ content_type = Column(String(20), nullable=False) # "text" or "url"
16
+ risk_level = Column(String(20), nullable=False) # "Safe", "Suspicious", "Scam"
17
+ confidence = Column(Float, nullable=False)
18
+ reasoning = Column(Text, nullable=True)
19
+ detected_language = Column(String(10), nullable=True)
20
+ processing_time = Column(Float, nullable=True)
21
+ additional_data = Column(JSON, nullable=True)
22
+ created_at = Column(DateTime(timezone=True), server_default=func.now())
23
+ updated_at = Column(DateTime(timezone=True), onupdate=func.now())
24
+
25
+
26
+ class WhatsAppAnalysis(Base):
27
+ __tablename__ = "whatsapp_analyses"
28
+
29
+ id = Column(Integer, primary_key=True, index=True)
30
+ message_id = Column(String(100), unique=True, nullable=False)
31
+ sender = Column(String(100), nullable=False)
32
+ content = Column(Text, nullable=False)
33
+ message_type = Column(String(20), default="text") # "text" or "voice"
34
+ risk_level = Column(String(20), nullable=False)
35
+ confidence = Column(Float, nullable=False)
36
+ reasoning = Column(Text, nullable=True)
37
+ processing_time = Column(Float, nullable=True)
38
+ created_at = Column(DateTime(timezone=True), server_default=func.now())
39
+ updated_at = Column(DateTime(timezone=True), onupdate=func.now())
movies2.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9af85cb7bfdc0c9293b2d87f7d460883abae325f306fdc827737b54498171232
3
+ size 2330653
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ streamlit
2
+ pandas
3
+ requests
4
+ scikit-learn
5
+ # gdown
6
+ scipy
similarity2.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2c667743c76a4a1e19bf3a5793c93a3ffb293081cb455383a6984fc2fa5d1376
3
+ size 184781252
test.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+
3
+ print("=== WITHOUT SEED (Random Results) ===")
4
+ print("Run 1:")
5
+ print("Random numbers:", np.random.rand(5))
6
+ print("Random integers:", np.random.randint(1, 100, 3))
7
+
8
+ print("\nRun 2:")
9
+ print("Random numbers:", np.random.rand(5))
10
+ print("Random integers:", np.random.randint(1, 100, 3))
11
+
12
+ print("\n=== WITH SEED (Reproducible Results) ===")
13
+ print("Run 1 with seed=23:")
14
+ np.random.seed(23)
15
+ print("Random numbers:", np.random.rand(5))
16
+ print("Random integers:", np.random.randint(1, 100, 3))
17
+
18
+ print("\nRun 2 with seed=23:")
19
+ np.random.seed(23) # Reset to same seed
20
+ print("Random numbers:", np.random.rand(5))
21
+ print("Random integers:", np.random.randint(1, 100, 3))
22
+
23
+ print("\n=== PRACTICAL EXAMPLE: Data Splitting ===")
24
+ # Imagine you're splitting data for machine learning
25
+ data = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
26
+
27
+ print("Without seed - different splits each time:")
28
+ shuffled1 = np.random.permutation(data)
29
+ train1, test1 = shuffled1[:7], shuffled1[7:]
30
+ print(f"Train: {train1}, Test: {test1}")
31
+
32
+ shuffled2 = np.random.permutation(data)
33
+ train2, test2 = shuffled2[:7], shuffled2[7:]
34
+ print(f"Train: {train2}, Test: {test2}")
35
+
36
+ print("\nWith seed - identical splits every time:")
37
+ np.random.seed(42)
38
+ shuffled1 = np.random.permutation(data)
39
+ train1, test1 = shuffled1[:7], shuffled1[7:]
40
+ print(f"Train: {train1}, Test: {test1}")
41
+
42
+ np.random.seed(42) # Same seed
43
+ shuffled2 = np.random.permutation(data)
44
+ train2, test2 = shuffled2[:7], shuffled2[7:]
45
+ print(f"Train: {train2}, Test: {test2}")
46
+
47
+ print("\n=== KEY POINTS ===")
48
+ print("1. Without seed: Results change every time you run")
49
+ print("2. With seed: Same results every time (reproducible)")
50
+ print("3. Different seed values give different sequences")
51
+ print("4. Essential for scientific reproducibility!")