Arpitkr/fraud-detection-ui
0
1from dotenv import load_dotenv2load_dotenv()3 4import streamlit as st5import requests6import json7from groq import Groq8from src.rag_engine import rag_engine9import os10from src.knowledge_base import FRAUD_KNOWLEDGE_BASE11from collections import defaultdict12 13client = Groq(api_key=os.getenv("GROQ_API_KEY"))14 15st.set_page_config(page_title="Fraud Detection System", layout="centered")16st.title("Credit Card Fraud Detection")17st.divider()18 19sample_fraud = {20 "Time": 406.0, "V1": -2.3122, "V2": 1.9520, "V3": -1.6099,21 "V4": 3.9979, "V5": -0.5222, "V6": -1.4265, "V7": -2.5374,22 "V8": 1.3917, "V9": -2.7701, "V10": -2.7723, "V11": 3.2020,23 "V12": -2.8999, "V13": -0.5952, "V14": -4.2893, "V15": 0.3897,24 "V16": -1.1407, "V17": -2.8301, "V18": -0.0168, "V19": 0.4170,25 "V20": 0.1269, "V21": 0.5172, "V22": -0.0350, "V23": -0.4652,26 "V24": 0.3202, "V25": 0.0445, "V26": 0.1778, "V27": 0.2611,27 "V28": -0.1433, "Amount": 149.6228}29 30V_DEFAULTS = {f"V{i}": 0.0 for i in range(1, 29)}31 32 33def safe_groq_completion(messages, max_tokens=200, temperature=0.3):34 try:35 response = client.chat.completions.create(36 model="openai/gpt-oss-20b",37 messages=messages,38 max_tokens=max_tokens,39 temperature=temperature40 )41 return response.choices[0].message.content.strip(), None42 43 except Exception as e:44 err = str(e).lower()45 46 if "quota" in err or "rate limit" in err or "429" in err or "forbidden" in err:47 return None, "Groq quota exceeded. Please try again in a moment."48 49 return None, f"LLM error: {str(e)}"50 51 52def run_prediction(payload):53 try:54 response = requests.post(55 "https://fraud-api-kzt3.onrender.com/predict",56 json=payload,57 timeout=3058 )59 response.raise_for_status()60 result = response.json()61 62 if "is_fraud" not in result:63 st.error(f"API Error: {result}")64 return65 66 st.subheader("Prediction Result")67 68 if result["is_fraud"]:69 st.error("FRAUDULENT TRANSACTION DETECTED!")70 else:71 st.success("Legitimate Transaction")72 73 col1, col2, col3, col4 = st.columns(4)74 with col1:75 st.metric("Fraud", "YES" if result["is_fraud"] else "NO")76 with col2:77 st.metric("Probability", f"{result['fraud_probability']*100:.1f}%")78 with col3:79 st.metric("Risk Level", result["risk_level"])80 with col4:81 st.metric("Action", result.get("suggested_action", "N/A"))82 83 st.divider()84 st.markdown("### ๐ค Agent Pipeline")85 risk = result["risk_level"]86 action = result.get("suggested_action", "N/A")87 action_color = "๐ด" if risk == "HIGH" else "๐ก" if risk == "MEDIUM" else "๐ข"88 st.markdown(f"""89| Step | Output |90|------|--------|91| 1๏ธ Predict | Fraud: **{'YES' if result['is_fraud'] else 'NO'}** โ {result['fraud_probability']*100:.1f}% probability |92| 2๏ธ Risk Level | {action_color} **{risk}** |93| 3๏ธ Suggested Action | **{action}** |94""")95 96 similar = result.get("similar_cases", [])97 if similar:98 st.markdown(f"### Similar Past Fraud Cases ({len(similar)} found)")99 for i, c in enumerate(similar, 1):100 st.markdown(101 f"**Case {i}** โ Amount: `${c['amount']}` | "102 f"Probability: `{c['fraud_probability']*100:.1f}%` | "103 f"Risk: `{c['risk_level']}` | "104 f"Time: `{c['timestamp']}`"105 )106 else:107 st.markdown("### Similar Past Fraud Cases")108 st.caption("No similar past fraud cases found yet. Run more fraud predictions to build history.")109 110 if result["is_fraud"]:111 st.divider()112 st.markdown("### RAG Fraud Analysis")113 114 with st.spinner("Retrieving relevant fraud knowledge..."):115 query = (116 f"V14={payload['V14']:.2f} V10={payload['V10']:.2f} "117 f"V12={payload['V12']:.2f} amount={payload['Amount']} fraud detected"118 )119 retrieved_docs = rag_engine.retrieve(query, top_k=3)120 121 st.markdown("**Retrieved Knowledge Chunks**")122 for i, doc in enumerate(retrieved_docs, 1):123 with st.expander(f"{i}. [{doc['category']}] {doc['title']} โ relevance: {doc['score']:.3f}"):124 st.write(doc["content"])125 126 with st.spinner("Generating grounded explanation..."):127 rag_prompt = rag_engine.build_rag_prompt(payload, result["fraud_probability"], retrieved_docs)128 rag_explanation, rag_error = safe_groq_completion(129 messages=[{"role": "user", "content": rag_prompt}],130 max_tokens=200131 )132 133 if rag_explanation:134 st.info(rag_explanation)135 else:136 st.warning(rag_error or "Explanation unavailable right now โ try again in a moment.")137 138 st.caption("Powered by RAG (sentence-transformers + FAISS cosine similarity) + GPT OSS 20B via Groq")139 140 elif result.get("explanation"):141 st.divider()142 st.markdown("### AI Fraud Analysis")143 st.info(result["explanation"])144 st.caption("Powered by GPT OSS 20B via Groq")145 146 st.session_state["last_result"] = result147 st.session_state["last_payload"] = payload148 149 except requests.exceptions.RequestException as e:150 st.error(f"API request failed: {e}")151 except Exception as e:152 st.error(f"Something went wrong: {e}")153 154 155tab1, tab2, tab3, tab4 = st.tabs(["Fraud Detector", "Natural Language", "AI Chat", "Knowledge Base"])156 157 158with tab1:159 st.markdown("Enter transaction details below to check if it's fraudulent.")160 161 if st.button("Load Sample Fraud Transaction", use_container_width=True):162 for key, val in sample_fraud.items():163 st.session_state[key] = val164 165 st.subheader("Transaction Details")166 col1, col2 = st.columns(2)167 with col1:168 Time = st.number_input("Time", value=st.session_state.get("Time", 0.0))169 Amount = st.number_input("Amount ($)", value=st.session_state.get("Amount", 0.0))170 with col2:171 st.markdown("**V1 โ V28** (PCA features)")172 173 st.markdown("**PCA Features (V1 - V28)**")174 cols = st.columns(4)175 v_values = {}176 for i in range(1, 29):177 col_idx = (i - 1) % 4178 with cols[col_idx]:179 v_values[f"V{i}"] = st.number_input(180 f"V{i}",181 value=st.session_state.get(f"V{i}", 0.0),182 format="%.4f"183 )184 185 st.divider()186 187 if st.button("Predict", use_container_width=True):188 payload = {"Time": Time, "Amount": Amount}189 payload.update(v_values)190 run_prediction(payload)191 192 193with tab2:194 st.markdown("### ๐ฃ๏ธ Describe a Transaction in Plain English")195 st.caption("Describe the transaction naturally โ the AI will extract features and run fraud detection.")196 197 st.markdown("""198**Try these examples:**199- `Transaction of $500 at 2AM from an unknown location`200- `Small $12 purchase at a local coffee shop during business hours`201- `$3000 international wire transfer at midnight on a new device`202- `Contactless $80 tap payment at grocery store at 6PM`203""")204 205 nl_input = st.text_area(206 "Describe the transaction",207 placeholder="e.g. Transaction of $500 at 2AM from an unknown location using a new device...",208 height=100209 )210 211 if st.button("Analyze Transaction", use_container_width=True):212 if not nl_input.strip():213 st.warning("Please describe a transaction first.")214 else:215 with st.spinner("Parsing transaction with AI..."):216 parse_prompt = f"""You are a credit card fraud detection feature extractor.217 218A user described a transaction in plain English. Extract structured features for a fraud detection model.219 220The model uses these key PCA features. Estimate values based on the transaction description:221- V14: Most important fraud signal. Normal: near 0. Suspicious: -2 to -5222- V10: Second most important. Suspicious: -2 to -4223- V12: Third most important. Suspicious: -2 to -4224- V17: Fourth. Suspicious: -2 to -3225- V4: High positive (2 to 4) = suspicious geographic anomaly226- All other V features: set to 0.0227 228Risk factors that push V14/V10/V12/V17 negative and V4 positive:229- Late night (12AM-4AM): high risk230- Unknown/foreign location: high risk231- Large amount (>$500): moderate risk232- New device or card: high risk233- International transaction: high risk234- Online/card-not-present: moderate risk235- Normal business hours + known location: low risk236- Small everyday purchase: low risk237 238Transaction: "{nl_input}"239 240Respond ONLY with valid JSON, no explanation:241{{242 "Amount": <number>,243 "Time": <seconds from midnight, e.g. 2AM = 7200>,244 "V4": <number>,245 "V10": <number>,246 "V12": <number>,247 "V14": <number>,248 "V17": <number>,249 "reasoning": "<one sentence explaining your risk assessment>"250}}"""251 252 raw, parse_error = safe_groq_completion(253 messages=[{"role": "user", "content": parse_prompt}],254 max_tokens=300255 )256 257 if not raw:258 st.error(parse_error or "Could not parse transaction right now.")259 else:260 try:261 start = raw.find("{")262 end = raw.rfind("}") + 1263 parsed = json.loads(raw[start:end])264 reasoning = parsed.pop("reasoning", "")265 266 payload = {**V_DEFAULTS}267 payload["Time"] = float(parsed.get("Time", 0))268 payload["Amount"] = float(parsed.get("Amount", 0))269 for key in ["V4", "V10", "V12", "V14", "V17"]:270 if key in parsed:271 payload[key] = float(parsed[key])272 273 st.markdown("### Extracted Features")274 c1, c2, c3 = st.columns(3)275 with c1:276 st.metric("Amount", f"${payload['Amount']:.2f}")277 with c2:278 h = int(payload['Time'] // 3600)279 st.metric("Time", f"{h:02d}:00")280 with c3:281 st.metric("V14 (key signal)", f"{payload['V14']:.2f}")282 283 c4, c5, c6 = st.columns(3)284 with c4:285 st.metric("V10", f"{payload['V10']:.2f}")286 with c5:287 st.metric("V12", f"{payload['V12']:.2f}")288 with c6:289 st.metric("V4", f"{payload['V4']:.2f}")290 291 if reasoning:292 st.info(f"**AI Reasoning:** {reasoning}")293 294 st.divider()295 run_prediction(payload)296 297 except Exception as e:298 st.error(f"Failed to parse AI response: {e}")299 st.code(raw)300 301 302with tab3:303 st.markdown("### Ask the AI about fraud detection")304 st.caption("Ask anything about the prediction, features, or fraud detection in general.")305 306 if "chat_history" not in st.session_state:307 st.session_state.chat_history = []308 309 system_prompt = """You are an expert AI assistant specializing in credit card fraud detection.310You help users understand fraud predictions made by an XGBoost machine learning model.311The dataset uses PCA-transformed features V1-V28 (anonymized for privacy), plus Time and Amount.312Key fraud indicators: V14, V10, V12, V17 (strongly negative = high fraud risk), V4 (high positive = suspicious).313Be concise, clear, and helpful. Explain technical concepts in simple terms."""314 315 if "last_result" in st.session_state:316 r = st.session_state["last_result"]317 p = st.session_state["last_payload"]318 system_prompt += f"""319 320Latest prediction context:321- Fraud detected: {r['is_fraud']}322- Probability: {r['fraud_probability']*100:.1f}%323- Risk level: {r['risk_level']}324- Suggested action: {r.get('suggested_action', 'N/A')}325- Amount: ${p['Amount']}326- Key features: V14={p['V14']:.4f}, V10={p['V10']:.4f}, V4={p['V4']:.4f}, V12={p['V12']:.4f}327- AI explanation: {r.get('explanation', 'N/A')}"""328 329 for msg in st.session_state.chat_history:330 with st.chat_message(msg["role"]):331 st.write(msg["content"])332 333 user_input = st.chat_input("Ask me anything about fraud detection...")334 335 if user_input:336 chat_docs = rag_engine.retrieve(user_input, top_k=2)337 rag_context = "\n\n".join([f"[{d['category']}] {d['title']}: {d['content']}" for d in chat_docs])338 enhanced_system = system_prompt + f"\n\nRelevant knowledge:\n{rag_context}"339 340 st.session_state.chat_history.append({"role": "user", "content": user_input})341 with st.chat_message("user"):342 st.write(user_input)343 344 with st.chat_message("assistant"):345 with st.spinner("Thinking..."):346 messages = [{"role": "system", "content": enhanced_system}]347 messages += st.session_state.chat_history348 reply, chat_error = safe_groq_completion(349 messages=messages,350 max_tokens=300351 )352 353 if reply:354 st.write(reply)355 else:356 reply = "AI chat is temporarily unavailable because the model quota was exceeded. Please try again shortly."357 st.warning(reply)358 359 st.session_state.chat_history.append({"role": "assistant", "content": reply})360 361 if st.button("Clear Chat"):362 st.session_state.chat_history = []363 st.rerun()364 365 366with tab4:367 st.markdown("### Fraud Knowledge Base")368 st.caption(f"{len(rag_engine.documents)} documents across 6 categories")369 370 by_category = defaultdict(list)371 for doc in FRAUD_KNOWLEDGE_BASE:372 by_category[doc["category"]].append(doc)373 374 for category, docs in by_category.items():375 st.markdown(f"#### {category} ({len(docs)} docs)")376 for doc in docs:377 with st.expander(doc["title"]):378 st.write(doc["content"])379 st.divider()