Neeraj Sathish Kumar commited on
Commit
b031340
Β·
1 Parent(s): 8795976

/get-random and /llm-analyse added

Browse files
app.py CHANGED
@@ -3,10 +3,14 @@ import sys
3
  import joblib
4
  import pandas as pd
5
  from typing import Dict, Any, List, Union, Optional
6
- from fastapi import FastAPI, HTTPException
7
  from pydantic import BaseModel, Field
8
  import numpy as np
9
  import warnings
 
 
 
 
10
 
11
  # Suppress sklearn version warnings
12
  warnings.filterwarnings("ignore", category=UserWarning, module="sklearn.base")
@@ -59,6 +63,10 @@ NUMERICAL_FEATURES = [
59
  # Ensure the order matches the columns fed to the ColumnTransformer during training
60
  EXPECTED_FEATURES = CATEGORICAL_FEATURES + NUMERICAL_FEATURES
61
 
 
 
 
 
62
  # --- FASTAPI SETUP ---
63
  app = FastAPI(
64
  title="Credit Card Fraud Detection API",
@@ -67,13 +75,24 @@ app = FastAPI(
67
  )
68
 
69
  class SingleTransactionPayload(BaseModel):
70
- model_name: str = Field(..., description="Model alias (e.g., 'calibrated_xgb', 'calibrated_rf', 'calibrated_dt').")
71
  features: Dict[str, Any] = Field(..., description="Single transaction record for prediction.")
72
 
73
  class MultipleTransactionsPayload(BaseModel):
74
- model_name: str = Field(..., description="Model alias (e.g., 'calibrated_xgb', 'calibrated_rf', 'calibrated_dt').")
75
  features: List[Dict[str, Any]] = Field(..., description="List of transaction records for prediction.")
76
 
 
 
 
 
 
 
 
 
 
 
 
77
  # --- LOAD MODELS AT STARTUP ---
78
  def load_pipelines():
79
  """Load all ML model pipelines"""
@@ -88,7 +107,7 @@ def load_pipelines():
88
  if not os.path.exists(filename):
89
  abs_path = os.path.abspath(filename)
90
  print(f"❌ Model file not found: {filename}")
91
- print(f" Expected at: {abs_path}")
92
  continue
93
 
94
  # Get file info
@@ -101,24 +120,51 @@ def load_pipelines():
101
 
102
  except AttributeError as e:
103
  print(f"❌ Compatibility error loading {filename}")
104
- print(f" Error: {e}")
105
- print(f" πŸ’‘ This usually means the model was saved with a different sklearn version")
106
- print(f" πŸ’‘ Try re-training and saving the model with sklearn {sklearn.__version__}")
107
  except Exception as e:
108
  print(f"❌ Failed to load {filename}")
109
- print(f" Error type: {type(e).__name__}")
110
- print(f" Error message: {e}")
111
 
112
  if not MODELS:
113
- print("⚠️ No models loaded. Predictions will fail.")
114
- print(" πŸ’‘ Ensure .pkl files are in the same directory as app.py (or subdirectories like model_outputs/)")
115
- print(" πŸ’‘ Check that models were saved with compatible sklearn version")
116
  else:
117
  print(f"βœ… Successfully loaded {len(MODELS)} model(s): {list(MODELS.keys())}")
118
 
119
  # Load models on import
120
  load_pipelines()
121
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  # --- HELPER FUNCTION: PREPARE FEATURES (WITH FIX) ---
123
  def prepare_features(features_list: List[Dict[str, Any]]) -> pd.DataFrame:
124
  """
@@ -144,10 +190,32 @@ def prepare_features(features_list: List[Dict[str, Any]]) -> pd.DataFrame:
144
 
145
  # Convert categorical columns to category dtype (as done during training)
146
  for col in CATEGORICAL_FEATURES:
 
 
 
147
  df_features[col] = df_features[col].astype("category")
148
 
149
  return df_features
150
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
  # --- FASTAPI ENDPOINTS ---
152
  @app.get("/")
153
  async def root():
@@ -162,6 +230,8 @@ async def root():
162
  "models": "/models",
163
  "predict": "/predict (POST) - Single transaction",
164
  "predict_multiple": "/predict_multiple (POST) - Multiple transactions",
 
 
165
  "docs": "/docs"
166
  },
167
  "response_format": {
@@ -175,6 +245,10 @@ async def root():
175
  "min_fraud_score": "float",
176
  "max_fraud_score": "float"
177
  }
 
 
 
 
178
  }
179
  }
180
  }
@@ -183,10 +257,12 @@ async def root():
183
  async def health_check():
184
  """Health check endpoint"""
185
  return {
186
- "status": "healthy" if MODELS else "degraded",
187
  "version": VERSION,
188
  "models_loaded": list(MODELS.keys()),
189
- "model_count": len(MODELS)
 
 
190
  }
191
 
192
  @app.get("/models")
@@ -198,6 +274,59 @@ async def list_models():
198
  "model_files": MODEL_MAP,
199
  "version": VERSION
200
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
 
202
  @app.post("/predict")
203
  async def predict_single(payload: SingleTransactionPayload):
@@ -308,6 +437,80 @@ async def predict_multiple(payload: MultipleTransactionsPayload):
308
  detail=f"Prediction execution failed: {type(e).__name__}: {str(e)}"
309
  )
310
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
311
  # For local development
312
  if __name__ == "__main__":
313
  import uvicorn
 
3
  import joblib
4
  import pandas as pd
5
  from typing import Dict, Any, List, Union, Optional
6
+ from fastapi import FastAPI, HTTPException, Query
7
  from pydantic import BaseModel, Field
8
  import numpy as np
9
  import warnings
10
+ import random
11
+ import google.generativeai as genai
12
+ import json
13
+ import re
14
 
15
  # Suppress sklearn version warnings
16
  warnings.filterwarnings("ignore", category=UserWarning, module="sklearn.base")
 
63
  # Ensure the order matches the columns fed to the ColumnTransformer during training
64
  EXPECTED_FEATURES = CATEGORICAL_FEATURES + NUMERICAL_FEATURES
65
 
66
+ # --- DATA CONSTANTS ---
67
+ DATA_FILE_PATH = "data/filteredTest.parquet"
68
+ DATA_DF: Optional[pd.DataFrame] = None # Global variable to cache the data
69
+
70
  # --- FASTAPI SETUP ---
71
  app = FastAPI(
72
  title="Credit Card Fraud Detection API",
 
75
  )
76
 
77
  class SingleTransactionPayload(BaseModel):
78
+ model_name: str = Field(..., description="Model alias (e.g., 'decision_tree', 'random_forest', 'xgboost').")
79
  features: Dict[str, Any] = Field(..., description="Single transaction record for prediction.")
80
 
81
  class MultipleTransactionsPayload(BaseModel):
82
+ model_name: str = Field(..., description="Model alias (e.g., 'decision_tree', 'random_forest', 'xgboost').")
83
  features: List[Dict[str, Any]] = Field(..., description="List of transaction records for prediction.")
84
 
85
+ class LLMAnalysePayload(BaseModel):
86
+ transactions: List[Dict[str, Any]] = Field(..., description="List of transaction records with 22 fields including fraud_score, STATUS, etc.")
87
+
88
+ # Configure Gemini API
89
+ GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
90
+ if GEMINI_API_KEY:
91
+ genai.configure(api_key=GEMINI_API_KEY)
92
+ print("βœ… Gemini API configured")
93
+ else:
94
+ print("⚠️ GEMINI_API_KEY not set in environment variables. LLM endpoint will fail.")
95
+
96
  # --- LOAD MODELS AT STARTUP ---
97
  def load_pipelines():
98
  """Load all ML model pipelines"""
 
107
  if not os.path.exists(filename):
108
  abs_path = os.path.abspath(filename)
109
  print(f"❌ Model file not found: {filename}")
110
+ print(f" Β  Expected at: {abs_path}")
111
  continue
112
 
113
  # Get file info
 
120
 
121
  except AttributeError as e:
122
  print(f"❌ Compatibility error loading {filename}")
123
+ print(f" Β  Error: {e}")
124
+ print(f" Β  πŸ’‘ This usually means the model was saved with a different sklearn version")
125
+ print(f" Β  πŸ’‘ Try re-training and saving the model with sklearn {sklearn.__version__}")
126
  except Exception as e:
127
  print(f"❌ Failed to load {filename}")
128
+ print(f" Β  Error type: {type(e).__name__}")
129
+ print(f" Β  Error message: {e}")
130
 
131
  if not MODELS:
132
+ print("⚠️  No models loaded. Predictions will fail.")
133
+ print(" Β  πŸ’‘ Ensure .pkl files are in the same directory as app.py (or subdirectories like model_outputs/)")
134
+ print(" Β  πŸ’‘ Check that models were saved with compatible sklearn version")
135
  else:
136
  print(f"βœ… Successfully loaded {len(MODELS)} model(s): {list(MODELS.keys())}")
137
 
138
  # Load models on import
139
  load_pipelines()
140
 
141
+ # --- HELPER FUNCTION: CACHE DATA ---
142
+ def load_data_file() -> Optional[pd.DataFrame]:
143
+ """Load the Parquet data file into the global DATA_DF variable."""
144
+ global DATA_DF
145
+ if DATA_DF is not None:
146
+ return DATA_DF
147
+
148
+ try:
149
+ if not os.path.exists(DATA_FILE_PATH):
150
+ abs_path = os.path.abspath(DATA_FILE_PATH)
151
+ print(f"❌ Data file not found: {DATA_FILE_PATH}")
152
+ print(f" Β  Expected at: {abs_path}")
153
+ return None
154
+
155
+ print(f"πŸ’Ύ Loading data from {DATA_FILE_PATH}...")
156
+ # Use pyarrow engine for better performance with parquet
157
+ DATA_DF = pd.read_parquet(DATA_FILE_PATH, engine='pyarrow')
158
+ print(f"βœ… Successfully loaded data with {len(DATA_DF)} rows.")
159
+ return DATA_DF
160
+
161
+ except Exception as e:
162
+ print(f"❌ Failed to load data file: {e}")
163
+ return None
164
+
165
+ # Load data on import for the new endpoint
166
+ load_data_file()
167
+
168
  # --- HELPER FUNCTION: PREPARE FEATURES (WITH FIX) ---
169
  def prepare_features(features_list: List[Dict[str, Any]]) -> pd.DataFrame:
170
  """
 
190
 
191
  # Convert categorical columns to category dtype (as done during training)
192
  for col in CATEGORICAL_FEATURES:
193
+ # NOTE: Ensure that all categories present here were also present during training
194
+ # For a simple API, we rely on the model's pipeline to handle unseen categories
195
+ # (usually by converting them to NaN or a dummy 'unseen' category).
196
  df_features[col] = df_features[col].astype("category")
197
 
198
  return df_features
199
 
200
+ def extract_json_from_markdown(text: str) -> str:
201
+ """
202
+ Extract JSON content from markdown code block.
203
+ Handles cases where the LLM wraps the output in ```json ... ```
204
+ """
205
+ # Look for ```json ... ```
206
+ match = re.search(r'```(?:json)?\s*\n?(.*?)\n?```', text, re.DOTALL | re.IGNORECASE)
207
+ if match:
208
+ json_str = match.group(1).strip()
209
+ else:
210
+ # Fallback: strip any leading/trailing whitespace and assume it's raw JSON
211
+ json_str = text.strip()
212
+
213
+ # Clean up common issues: remove extra newlines, fix quotes if needed
214
+ json_str = re.sub(r'\n\s*', ' ', json_str) # Collapse newlines to spaces
215
+ json_str = re.sub(r'\\n', ' ', json_str) # Replace escaped newlines
216
+
217
+ return json_str
218
+
219
  # --- FASTAPI ENDPOINTS ---
220
  @app.get("/")
221
  async def root():
 
230
  "models": "/models",
231
  "predict": "/predict (POST) - Single transaction",
232
  "predict_multiple": "/predict_multiple (POST) - Multiple transactions",
233
+ "random_data": "/get-random-data (GET) - Get sample data for testing", # ADDED
234
+ "llm_analyse": "/llm-analyse (POST) - LLM analysis of transactions",
235
  "docs": "/docs"
236
  },
237
  "response_format": {
 
245
  "min_fraud_score": "float",
246
  "max_fraud_score": "float"
247
  }
248
+ },
249
+ "llm_analyse": {
250
+ "fraud_score": "float (0-1, e.g., 0.12 for 12%)",
251
+ "explanation": "str"
252
  }
253
  }
254
  }
 
257
  async def health_check():
258
  """Health check endpoint"""
259
  return {
260
+ "status": "healthy" if MODELS and DATA_DF is not None else "degraded",
261
  "version": VERSION,
262
  "models_loaded": list(MODELS.keys()),
263
+ "model_count": len(MODELS),
264
+ "data_loaded": DATA_DF is not None,
265
+ "gemini_configured": GEMINI_API_KEY is not None
266
  }
267
 
268
  @app.get("/models")
 
274
  "model_files": MODEL_MAP,
275
  "version": VERSION
276
  }
277
+
278
+ @app.get("/get-random-data")
279
+ async def get_random_data(
280
+ num_rows: int = Query(
281
+ 10,
282
+ ge=1,
283
+ le=1000,
284
+ description="The number of random rows to return (between 1 and 1000)."
285
+ )
286
+ ):
287
+ """
288
+ Retrieves a specified number of random transaction records from the dataset,
289
+ excluding the 'is_fraud' column, suitable for testing the prediction endpoints.
290
+ """
291
+ df = load_data_file()
292
+
293
+ if df is None:
294
+ raise HTTPException(
295
+ status_code=500,
296
+ detail=f"Data file not loaded. Check server logs for {DATA_FILE_PATH}"
297
+ )
298
+
299
+ total_rows = len(df)
300
+ if num_rows > total_rows:
301
+ num_rows = total_rows
302
+
303
+ try:
304
+ # Sample the data randomly
305
+ random_sample_df = df.sample(n=num_rows).copy()
306
+
307
+ # Drop the 'is_fraud' column as requested
308
+ if 'is_fraud' in random_sample_df.columns:
309
+ random_sample_df = random_sample_df.drop(columns=['is_fraud'])
310
+
311
+ # Ensure the output columns match the expected input features for the predict endpoints
312
+ final_cols = [col for col in EXPECTED_FEATURES if col in random_sample_df.columns]
313
+ random_sample_df = random_sample_df[final_cols]
314
+
315
+ # Convert to a list of dicts (JSON serializable format)
316
+ data_records = random_sample_df.to_dict(orient='records')
317
+
318
+ return {
319
+ "success": True,
320
+ "message": f"Returned {len(data_records)} random records.",
321
+ "data": data_records
322
+ }
323
+
324
+ except Exception as e:
325
+ raise HTTPException(
326
+ status_code=500,
327
+ detail=f"Error processing data request: {str(e)}"
328
+ )
329
+
330
 
331
  @app.post("/predict")
332
  async def predict_single(payload: SingleTransactionPayload):
 
437
  detail=f"Prediction execution failed: {type(e).__name__}: {str(e)}"
438
  )
439
 
440
+ @app.post("/llm-analyse")
441
+ async def llm_analyse(payload: LLMAnalysePayload):
442
+ """
443
+ LLM-based analysis of transactions using Gemini.
444
+
445
+ Expects a list of transactions with fields: fraud_score, STATUS, cc_num, merchant, category, amt, gender, state, zip, lat, long, city_pop, job, unix_time, merch_lat, merch_long, is_fraud, age, trans_hour, trans_day, trans_month, trans_weekday, distance
446
+
447
+ Converts to CSV, analyzes with Gemini, returns overall fraud_score (0-1) and explanation.
448
+ """
449
+ if not GEMINI_API_KEY:
450
+ raise HTTPException(
451
+ status_code=500,
452
+ detail="Gemini API key not configured. Set GEMINI_API_KEY environment variable."
453
+ )
454
+
455
+ transactions = payload.transactions
456
+ if not transactions:
457
+ raise HTTPException(
458
+ status_code=422,
459
+ detail="No transactions provided."
460
+ )
461
+
462
+ try:
463
+ # Convert to DataFrame and CSV string
464
+ df = pd.DataFrame(transactions)
465
+ csv_string = df.to_csv(index=False)
466
+
467
+ # Craft prompt
468
+ prompt = f"""
469
+ Analyze the following credit card transaction data (CSV format). Each row includes fraud_score (0-100 from ML model), STATUS, and other transaction details.
470
+
471
+ CSV Data:
472
+ {csv_string}
473
+
474
+ Instructions:
475
+ - Compute an overall fraud_score (0-1 scale, where 0.12 means 12% fraud probability) based on patterns in fraud_score, amounts (amt), categories (category), locations (lat/long vs merch_lat/merch_long), times (trans_hour, trans_day, etc.), and is_fraud labels.
476
+ - Consider thresholds: <0.5 good (low risk), 0.5-0.6 uncertain, >0.6 suspicious/critical.
477
+ - Provide a concise explanation of the overall assessment, highlighting key patterns (e.g., high average fraud_score, unusual spending).
478
+ - For CRITICAL (>0.6) or UNCERTAIN (0.5-0.6) transactions, specifically explain reasons for suspicion, such as unreasonably high amounts spent on categories like 'gas', 'grocery', etc., unusual distances, or time anomalies.
479
+ - Output ONLY valid JSON in this exact format: {{"fraud_score": <float 0-1>, "explanation": "<string explanation in brief>"}}
480
+ - Ensure fraud_score is a float (e.g., 0.12), rounded to 2 decimals if needed.
481
+ - explanation should be a brief string without line breaks or any formatting. And dont reveal any file structure or CSV data directly in the explanation
482
+ """
483
+
484
+ # Generate with Gemini
485
+ model = genai.GenerativeModel('gemini-2.5-flash-lite-preview-09-2025')
486
+ response = model.generate_content(prompt)
487
+
488
+ # Parse response as JSON with markdown extraction
489
+ try:
490
+ raw_response = response.text
491
+ json_str = extract_json_from_markdown(raw_response)
492
+ analysis_json = json.loads(json_str)
493
+ if not isinstance(analysis_json.get('fraud_score'), (int, float)) or not isinstance(analysis_json.get('explanation'), str):
494
+ raise ValueError("Invalid JSON structure from LLM")
495
+ except json.JSONDecodeError as je:
496
+ raise HTTPException(
497
+ status_code=500,
498
+ detail=f"Failed to parse LLM response as JSON: {str(je)}. Raw response: {raw_response}"
499
+ )
500
+ except ValueError as ve:
501
+ raise HTTPException(
502
+ status_code=500,
503
+ detail=f"Invalid LLM response structure: {str(ve)}. Raw response: {raw_response}"
504
+ )
505
+
506
+ return analysis_json
507
+
508
+ except Exception as e:
509
+ raise HTTPException(
510
+ status_code=500,
511
+ detail=f"LLM analysis failed: {type(e).__name__}: {str(e)}"
512
+ )
513
+
514
  # For local development
515
  if __name__ == "__main__":
516
  import uvicorn
data/filteredTest.parquet ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3308f6df15a5b76b07a081d2df179e774acfbad01eaf01908bed9c1c2192a4f3
3
+ size 24188561
data/filteredTrain.parquet ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7f64208a7c5b840aea3fe8c5bc4037d53675d7ec940b89ede69daa597e36c76c
3
+ size 55994057
stats/graphs/metrics.png ADDED
stats/graphs/precision-recall.png ADDED
stats/graphs/predict.png ADDED
stats/graphs/request_ram.png ADDED
stats/graphs/roc.png ADDED
stats/graphs/speed.png ADDED
stats/graphs/stats.png ADDED
stats/graphs/training_summary.png ADDED
test.py ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ import json
3
+ from typing import List, Dict, Any
4
+
5
+ # Endpoint URL (assuming the FastAPI server is running locally on port 7860)
6
+ BASE_URL = "http://localhost:7860"
7
+
8
+ def create_sample_transactions(num_transactions: int = 3) -> List[Dict[str, Any]]:
9
+ """
10
+ Generate sample transaction data for testing the /llm-analyse endpoint.
11
+ Includes all 22 required fields: fraud_score, STATUS, cc_num, merchant, category,
12
+ amt, gender, state, zip, lat, long, city_pop, job, unix_time, merch_lat,
13
+ merch_long, is_fraud, age, trans_hour, trans_day, trans_month, trans_weekday, distance.
14
+ """
15
+ samples = []
16
+ for i in range(num_transactions):
17
+ transaction = {
18
+ "fraud_score": round(10 + (i * 20), 2), # Vary fraud_score: 10, 30, 50 for example
19
+ "STATUS": "approved" if i < 2 else "declined", # Mix statuses
20
+ "cc_num": 4532015112830366 + i, # Fake CC numbers
21
+ "merchant": f"merchant_{i+1}",
22
+ "category": ["gas", "grocery", "entertainment"][i % 3],
23
+ "amt": round(50 + (i * 100), 2), # Increasing amounts: 50, 150, 250
24
+ "gender": "F" if i % 2 == 0 else "M",
25
+ "state": ["NY", "CA", "TX"][i % 3],
26
+ "zip": 10001 + i * 100,
27
+ "lat": 40.7128 + (i * 0.1),
28
+ "long": -74.0060 + (i * 0.1),
29
+ "city_pop": 8000000 - (i * 1000000),
30
+ "job": ["Lawyer", "Doctor", "Engineer"][i % 3],
31
+ "unix_time": 1640995200 + (i * 3600), # Sequential hours
32
+ "merch_lat": 40.7589 + (i * 0.05),
33
+ "merch_long": -73.9851 + (i * 0.05),
34
+ "is_fraud": 0 if i < 2 else 1,
35
+ "age": 30 + i * 5,
36
+ "trans_hour": (12 + i) % 24,
37
+ "trans_day": i + 1,
38
+ "trans_month": 12,
39
+ "trans_weekday": (i % 7) + 1,
40
+ "distance": round(5 + (i * 10), 2) # Increasing distance
41
+ }
42
+ samples.append(transaction)
43
+ return samples
44
+
45
+ def test_llm_analyse():
46
+ """
47
+ Test the /llm-analyse endpoint by sending sample transactions and printing the response.
48
+ """
49
+ endpoint = f"{BASE_URL}/llm-analyse"
50
+
51
+ # Prepare payload
52
+ payload = {
53
+ "transactions": create_sample_transactions(3)
54
+ }
55
+
56
+ print("πŸ“€ Sending request to /llm-analyse...")
57
+ print(json.dumps(payload, indent=2))
58
+ print("-" * 50)
59
+
60
+ try:
61
+ response = requests.post(endpoint, json=payload)
62
+ response.raise_for_status() # Raise an HTTPError for bad responses
63
+
64
+ result = response.json()
65
+ print("βœ… Response received:")
66
+ print(json.dumps(result, indent=2))
67
+
68
+ # Additional checks
69
+ if "fraud_score" in result and "explanation" in result:
70
+ fraud_score = result["fraud_score"]
71
+ explanation = result["explanation"]
72
+ print(f"\nπŸ“Š Overall Fraud Score: {fraud_score} ({fraud_score * 100:.1f}%)")
73
+ print(f"πŸ’‘ Explanation: {explanation}")
74
+
75
+ # Simple categorization
76
+ if fraud_score < 0.5:
77
+ print("🟒 Assessment: Good (Low Risk)")
78
+ elif 0.5 <= fraud_score <= 0.6:
79
+ print("🟑 Assessment: Uncertain")
80
+ else:
81
+ print("πŸ”΄ Assessment: Suspicious/Critical")
82
+ else:
83
+ print("⚠️ Unexpected response format.")
84
+
85
+ except requests.exceptions.RequestException as e:
86
+ print(f"❌ Request failed: {e}")
87
+ if hasattr(e.response, 'text'):
88
+ print(f"Server response: {e.response.text}")
89
+ except json.JSONDecodeError as e:
90
+ print(f"❌ Failed to parse JSON response: {e}")
91
+ print(f"Raw response: {response.text}")
92
+
93
+ if __name__ == "__main__":
94
+ # Run the test
95
+ test_llm_analyse()