oke39 commited on
Commit
69509f1
·
verified ·
1 Parent(s): 3f4c468

Upload 5 files

Browse files
Files changed (5) hide show
  1. app.py +217 -0
  2. columns.pkl +3 -0
  3. latest_checkpoint.h5 +3 -0
  4. scaler.pkl +3 -0
  5. shap_metadata.pkl +3 -0
app.py ADDED
@@ -0,0 +1,217 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import tensorflow as tf
4
+ import shap
5
+ import pickle
6
+ import os
7
+ import pandas as pd
8
+ import matplotlib.pyplot as plt
9
+ from groq import Groq
10
+
11
+ # --- 1. SETUP & CONFIG ---
12
+ st.set_page_config(page_title="Credit-Scout AI", layout="wide")
13
+
14
+ # CSS for "Bank" styling
15
+ st.markdown("""
16
+ <style>
17
+ .main { background-color: #f5f5f5; }
18
+ .stButton>button { background-color: #000044; color: white; width: 100%; }
19
+ .risk-high { color: #cc0000; font-weight: bold; font-size: 20px; }
20
+ .risk-low { color: #006600; font-weight: bold; font-size: 20px; }
21
+ </style>
22
+ """, unsafe_allow_html=True)
23
+
24
+ # Initialize Groq Client
25
+ # It looks for the key in Hugging Face Secrets
26
+ api_key = os.environ.get("GROQ_API_KEY")
27
+ if not api_key:
28
+ st.error("⚠️ GROQ_API_KEY not found in Secrets! The LLM explanation will fail.")
29
+ client = None
30
+ else:
31
+ client = Groq(api_key=api_key)
32
+
33
+ # --- 2. LOAD ARTIFACTS ---
34
+ @st.cache_resource
35
+ def load_resources():
36
+ # Load Model (CPU mode)
37
+ model = tf.keras.models.load_model('latest_checkpoint.h5')
38
+
39
+ # Load Pickles
40
+ with open('scaler.pkl', 'rb') as f:
41
+ scaler = pickle.load(f)
42
+ with open('columns.pkl', 'rb') as f:
43
+ columns = pickle.load(f)
44
+ with open('shap_metadata.pkl', 'rb') as f:
45
+ shap_data = pickle.load(f)
46
+
47
+ # Re-initialize Explainer
48
+ explainer = shap.GradientExplainer(model, shap_data['background_sample'])
49
+ return model, scaler, columns, explainer
50
+
51
+ # Load everything once
52
+ try:
53
+ model, scaler, columns, explainer = load_resources()
54
+ except Exception as e:
55
+ st.error(f"Error loading files: {e}. Did you upload .h5 and .pkl files?")
56
+ st.stop()
57
+
58
+ # --- 3. BUSINESS MAPPING ---
59
+ BUSINESS_MAP = {
60
+ 'step': 'Transaction Hour',
61
+ 'type_enc': 'Txn Type (Transfer/CashOut)',
62
+ 'amount': 'Transaction Amount',
63
+ 'oldbalanceOrg': 'Origin Acct Balance (Pre)',
64
+ 'newbalanceOrig': 'Origin Acct Balance (Post)',
65
+ 'oldbalanceDest': 'Recipient Acct Balance (Pre)',
66
+ 'newbalanceDest': 'Recipient Acct Balance (Post)',
67
+ 'errorBalanceOrig': 'Origin Math Discrepancy',
68
+ 'errorBalanceDest': 'Recipient Math Discrepancy'
69
+ }
70
+
71
+ # --- 4. EXPLANATION FUNCTION (GROQ API) ---
72
+ def generate_explanation_cloud(sample_idx_in_shap, shap_values, original_samples, feature_names, scaler):
73
+ # A. Inverse Transform to get Real Money
74
+ raw_scaled = original_samples.flatten()
75
+ real_values = scaler.inverse_transform(raw_scaled.reshape(1, -1)).flatten()
76
+
77
+ if isinstance(shap_values, list):
78
+ vals = shap_values[0]
79
+ else:
80
+ vals = shap_values
81
+ vals = vals.flatten()
82
+
83
+ # B. Prepare Data for LLM
84
+ feature_data = []
85
+ for i, col_name in enumerate(feature_names):
86
+ biz_name = BUSINESS_MAP.get(col_name, col_name)
87
+ feature_data.append((biz_name, real_values[i], vals[i]))
88
+
89
+ # Sort by absolute impact
90
+ feature_data.sort(key=lambda x: abs(x[2]), reverse=True)
91
+ total_shap_mass = sum([abs(v) for _, _, v in feature_data]) + 1e-9
92
+
93
+ data_lines = []
94
+ shap_lines = []
95
+
96
+ for name, real_val, shap_val in feature_data[:3]:
97
+ # Format Currency
98
+ if "Amount" in name or "Balance" in name:
99
+ val_str = f"${real_val:,.2f}"
100
+ else:
101
+ val_str = f"{real_val:.2f}"
102
+
103
+ contrib_pct = (abs(shap_val) / total_shap_mass) * 100
104
+ logic_hint = "ANOMALY (Increased Risk)" if shap_val > 0 else "CONSISTENT BEHAVIOR (Mitigated Risk)"
105
+
106
+ data_lines.append(f"- {name}: {val_str}")
107
+ shap_lines.append(f"- {name}: {logic_hint} | Contribution: {contrib_pct:.1f}%")
108
+
109
+ # C. Call Groq
110
+ if not client:
111
+ return "Error: Groq API Key missing."
112
+
113
+ prompt = f"""
114
+ You are a Senior Model Risk Examiner. Write a strict, short compliance explanation.
115
+
116
+ CONTEXT:
117
+ {chr(10).join(data_lines)}
118
+
119
+ RISK FACTORS:
120
+ {chr(10).join(shap_lines)}
121
+
122
+ Write a "Notice of Adverse Action" explanation.
123
+ Use the provided logic hints. Interpret negative SHAP as consistency.
124
+ Keep it under 150 words. Professional tone only. Add a standard disclaimer at the end.
125
+ """
126
+
127
+ try:
128
+ completion = client.chat.completions.create(
129
+ model="llama-3.1-70b-versatile",
130
+ messages=[{"role": "user", "content": prompt}],
131
+ temperature=0.1,
132
+ max_tokens=300,
133
+ )
134
+ return completion.choices[0].message.content
135
+ except Exception as e:
136
+ return f"LLM Error: {str(e)}"
137
+
138
+ # --- 5. SIDEBAR UI ---
139
+ st.sidebar.image("https://cdn-icons-png.flaticon.com/512/2666/2666505.png", width=100)
140
+ st.sidebar.title("💳 Transaction Details")
141
+
142
+ amount = st.sidebar.number_input("Amount ($)", value=350000.0)
143
+ old_bal = st.sidebar.number_input("Origin Old Balance ($)", value=1200000.0)
144
+ new_bal = st.sidebar.number_input("Origin New Balance ($)", value=850000.0)
145
+ txn_type = st.sidebar.selectbox("Type", ["TRANSFER", "CASH_OUT"])
146
+
147
+ # Auto-calculate math features
148
+ error_bal_orig = new_bal + amount - old_bal
149
+ st.sidebar.info(f"Math Discrepancy: ${error_bal_orig:.2f}")
150
+
151
+ # --- 6. MAIN APP LOGIC ---
152
+ st.title("🏦 Credit-Scout AI Risk Engine")
153
+ st.markdown("Real-time Fraud Detection with Llama 3.1 Explainability")
154
+
155
+ if st.sidebar.button("Analyze Transaction"):
156
+ with st.spinner("Analyzing Risk Patterns..."):
157
+ # 1. Preprocess
158
+ type_val = 0 if txn_type == 'TRANSFER' else 1
159
+
160
+ # Construct Input Array (Must match columns.pkl order exactly!)
161
+ # Standard PaySim columns: step, type, amount, oldBalOrg, newBalOrig, oldBalDest, newBalDest, errorOrig, errorDest
162
+ raw_features = np.array([
163
+ 150, # step (mock)
164
+ type_val,
165
+ amount,
166
+ old_bal,
167
+ new_bal,
168
+ 0.0, # oldbalanceDest (mock)
169
+ 0.0, # newbalanceDest (mock)
170
+ error_bal_orig,
171
+ 0.0 # errorBalanceDest (mock)
172
+ ]).reshape(1, -1)
173
+
174
+ # Scale & Reshape
175
+ scaled_features = scaler.transform(raw_features)
176
+ lstm_input = scaled_features.reshape(1, 1, 9)
177
+
178
+ # 2. Predict
179
+ risk_prob = model.predict(lstm_input)[0][0]
180
+
181
+ # 3. Explain (SHAP)
182
+ shap_vals = explainer.shap_values(lstm_input)
183
+
184
+ # 4. Display Results
185
+ col1, col2 = st.columns([1, 2])
186
+
187
+ with col1:
188
+ st.subheader("Risk Score")
189
+ st.metric(label="Fraud Probability", value=f"{risk_prob:.2%}")
190
+ if risk_prob > 0.8:
191
+ st.markdown('<p class="risk-high">⛔ FLAGGED</p>', unsafe_allow_html=True)
192
+ else:
193
+ st.markdown('<p class="risk-low">✅ APPROVED</p>', unsafe_allow_html=True)
194
+
195
+ with col2:
196
+ st.subheader("Model Logic (SHAP)")
197
+ # Fix SHAP plot dimensions
198
+ st.set_option('deprecation.showPyplotGlobalUse', False)
199
+ if isinstance(shap_vals, list):
200
+ shap_vals_plot = shap_vals[0]
201
+ else:
202
+ shap_vals_plot = shap_vals
203
+
204
+ fig = plt.figure()
205
+ shap.summary_plot(shap_vals_plot, raw_features, feature_names=columns, plot_type="bar", show=False)
206
+ st.pyplot(fig)
207
+
208
+ # 5. LLM Report
209
+ st.markdown("---")
210
+ st.subheader("📝 Audit Report (Llama 3.1)")
211
+ with st.spinner("Drafting Compliance Notice via Groq..."):
212
+ report = generate_explanation_cloud(0, shap_vals, lstm_input, columns, scaler)
213
+ st.success("Report Generated")
214
+ st.write(report)
215
+
216
+ else:
217
+ st.info(" Adjust transaction details in the sidebar to test the model.")
columns.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:feac7566c51c13889ccc7a38deba65f0a810259427f1bd3928c6136998eec502
3
+ size 148
latest_checkpoint.h5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6778348621ae9b28134b28017e24caf62feaeb9dedab5f9044685ec1a781543c
3
+ size 1024456
scaler.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e684224bb39e6477c255482b7deccab315dd261c1f94f25c7bcc64b72bd6e96d
3
+ size 538
shap_metadata.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c95ab9c7b97d14b502eeefaf2642f3bd6f4d30fd58cbdadf1be23873e1e689f2
3
+ size 14731