Spaces:
Running
Running
2026-06-05: UX polish — cleaner intro, distance slider, remove redundant button
Browse files
app.py
CHANGED
|
@@ -152,8 +152,7 @@ def _load_live():
|
|
| 152 |
_LIVE["explainer"] = None
|
| 153 |
|
| 154 |
|
| 155 |
-
def score_transaction(amt, category, hour, gender, state,
|
| 156 |
-
merch_lat, merch_long, city_pop, age_years):
|
| 157 |
if not config.LGBM_MODEL.exists():
|
| 158 |
return "Run training first.", None
|
| 159 |
_load_live()
|
|
@@ -164,12 +163,18 @@ def score_transaction(amt, category, hour, gender, state, home_lat, home_long,
|
|
| 164 |
unix_t = base.timestamp()
|
| 165 |
dob = (base - dt.timedelta(days=int(age_years * 365.25))).strftime("%Y-%m-%d")
|
| 166 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
txn = {
|
| 168 |
"cc_num": 9999999999999999, "amt": float(amt), "unix_time": unix_t,
|
| 169 |
"merchant": "demo_merchant", "category": category, "gender": gender,
|
| 170 |
-
"state": state, "lat":
|
| 171 |
-
"merch_lat":
|
| 172 |
-
"city_pop":
|
| 173 |
}
|
| 174 |
|
| 175 |
t0 = time.perf_counter()
|
|
@@ -231,7 +236,7 @@ def warmup_live_scoring():
|
|
| 231 |
"""
|
| 232 |
try:
|
| 233 |
_load_live()
|
| 234 |
-
score_transaction(100, "shopping_net", 12, "F", "NY",
|
| 235 |
from src.online import OnlineFeatureStore
|
| 236 |
_LIVE["store"] = OnlineFeatureStore()
|
| 237 |
print("[warmup] live scoring + SHAP ready")
|
|
@@ -415,10 +420,13 @@ On **Sparkov** (strong engineered features) cost-sensitive weighting *dominates*
|
|
| 415 |
_DESC = """
|
| 416 |
# 🛡️ Real-Time Credit Card Fraud Detection
|
| 417 |
|
| 418 |
-
|
| 419 |
-
|
| 420 |
-
|
| 421 |
-
|
|
|
|
|
|
|
|
|
|
| 422 |
"""
|
| 423 |
|
| 424 |
with gr.Blocks(title="Fraud Detection",
|
|
@@ -427,17 +435,17 @@ with gr.Blocks(title="Fraud Detection",
|
|
| 427 |
gr.Markdown(_DESC)
|
| 428 |
|
| 429 |
with gr.Tab("1. Model Performance"):
|
| 430 |
-
b1 = gr.Button("Load results", variant="primary")
|
| 431 |
with gr.Row():
|
| 432 |
p1 = gr.Plot()
|
| 433 |
p2 = gr.Plot()
|
| 434 |
md1 = gr.Markdown()
|
| 435 |
-
b1.click(performance_view, outputs=[p1, p2, md1], scroll_to_output=False)
|
| 436 |
demo.load(performance_view, outputs=[p1, p2, md1])
|
| 437 |
|
| 438 |
with gr.Tab("2. Live Scoring"):
|
| 439 |
-
gr.Markdown("### Score a transaction in real time\
|
| 440 |
-
"
|
|
|
|
|
|
|
| 441 |
with gr.Row():
|
| 442 |
with gr.Column():
|
| 443 |
amt = gr.Slider(1, 5000, value=850, label="Amount ($)")
|
|
@@ -446,23 +454,19 @@ with gr.Blocks(title="Fraud Detection",
|
|
| 446 |
"shopping_pos", "entertainment", "food_dining", "health_fitness",
|
| 447 |
"travel", "kids_pets", "home", "personal_care"],
|
| 448 |
value="shopping_net", label="Category")
|
| 449 |
-
hour = gr.Slider(0, 23, value=2, step=1, label="Hour of day (0
|
|
|
|
|
|
|
|
|
|
| 450 |
gender = gr.Dropdown(["F", "M"], value="F", label="Gender")
|
| 451 |
state = gr.Dropdown(["NY", "CA", "TX", "FL", "PA", "OH", "IL"],
|
| 452 |
value="NY", label="State")
|
| 453 |
-
with gr.Column():
|
| 454 |
-
home_lat = gr.Number(value=40.71, label="Cardholder home lat")
|
| 455 |
-
home_long = gr.Number(value=-74.0, label="Cardholder home long")
|
| 456 |
-
merch_lat = gr.Number(value=36.0, label="Merchant lat")
|
| 457 |
-
merch_long = gr.Number(value=-90.0, label="Merchant long")
|
| 458 |
-
city_pop = gr.Number(value=1000000, label="City population")
|
| 459 |
age_years = gr.Slider(18, 90, value=35, step=1, label="Cardholder age")
|
| 460 |
sbtn = gr.Button("Score Transaction", variant="primary")
|
| 461 |
smd = gr.Markdown()
|
| 462 |
splot = gr.Plot()
|
| 463 |
sbtn.click(score_transaction,
|
| 464 |
-
inputs=[amt, category, hour, gender, state,
|
| 465 |
-
merch_lat, merch_long, city_pop, age_years],
|
| 466 |
outputs=[smd, splot], scroll_to_output=False)
|
| 467 |
|
| 468 |
with gr.Tab("3. Explainability"):
|
|
|
|
| 152 |
_LIVE["explainer"] = None
|
| 153 |
|
| 154 |
|
| 155 |
+
def score_transaction(amt, category, hour, distance_km, gender, state, age_years):
|
|
|
|
| 156 |
if not config.LGBM_MODEL.exists():
|
| 157 |
return "Run training first.", None
|
| 158 |
_load_live()
|
|
|
|
| 163 |
unix_t = base.timestamp()
|
| 164 |
dob = (base - dt.timedelta(days=int(age_years * 365.25))).strftime("%Y-%m-%d")
|
| 165 |
|
| 166 |
+
# Place the merchant `distance_km` away from a fixed home location, so the
|
| 167 |
+
# user controls the geo-distance signal directly without typing coordinates.
|
| 168 |
+
home_lat, home_long = 40.71, -74.0 # fixed demo "home" (New York)
|
| 169 |
+
merch_lat = home_lat
|
| 170 |
+
merch_long = home_long + float(distance_km) / 85.0 # ~85 km per lon-degree at 40N
|
| 171 |
+
|
| 172 |
txn = {
|
| 173 |
"cc_num": 9999999999999999, "amt": float(amt), "unix_time": unix_t,
|
| 174 |
"merchant": "demo_merchant", "category": category, "gender": gender,
|
| 175 |
+
"state": state, "lat": home_lat, "long": home_long,
|
| 176 |
+
"merch_lat": merch_lat, "merch_long": merch_long,
|
| 177 |
+
"city_pop": 500000.0, "dob": dob,
|
| 178 |
}
|
| 179 |
|
| 180 |
t0 = time.perf_counter()
|
|
|
|
| 236 |
"""
|
| 237 |
try:
|
| 238 |
_load_live()
|
| 239 |
+
score_transaction(100, "shopping_net", 12, 50, "F", "NY", 35)
|
| 240 |
from src.online import OnlineFeatureStore
|
| 241 |
_LIVE["store"] = OnlineFeatureStore()
|
| 242 |
print("[warmup] live scoring + SHAP ready")
|
|
|
|
| 420 |
_DESC = """
|
| 421 |
# 🛡️ Real-Time Credit Card Fraud Detection
|
| 422 |
|
| 423 |
+
An end-to-end fraud system on the **Sparkov** dataset (1.85M transactions, ~0.5% fraud).
|
| 424 |
+
|
| 425 |
+
- **Model** — LightGBM (PR-AUC **0.97**) · plus a GraphSAGE GNN and an autoencoder baseline
|
| 426 |
+
- **Honest evaluation** — PR-AUC, cost-optimal thresholds, an imbalance study + real-data validation
|
| 427 |
+
- **Production-shaped** — leakage-safe features, SHAP explanations, drift monitoring, ~10 ms scoring
|
| 428 |
+
|
| 429 |
+
👉 **Try it:** open the **Live Scoring** tab, set an amount / time / distance, and click *Score*.
|
| 430 |
"""
|
| 431 |
|
| 432 |
with gr.Blocks(title="Fraud Detection",
|
|
|
|
| 435 |
gr.Markdown(_DESC)
|
| 436 |
|
| 437 |
with gr.Tab("1. Model Performance"):
|
|
|
|
| 438 |
with gr.Row():
|
| 439 |
p1 = gr.Plot()
|
| 440 |
p2 = gr.Plot()
|
| 441 |
md1 = gr.Markdown()
|
|
|
|
| 442 |
demo.load(performance_view, outputs=[p1, p2, md1])
|
| 443 |
|
| 444 |
with gr.Tab("2. Live Scoring"):
|
| 445 |
+
gr.Markdown("### Score a transaction in real time\n"
|
| 446 |
+
"Set the transaction details and click **Score**. Fraud rises with a high "
|
| 447 |
+
"amount, a late hour, and a large distance from home. Score several in a "
|
| 448 |
+
"row to build up the card's velocity history and watch the risk climb.")
|
| 449 |
with gr.Row():
|
| 450 |
with gr.Column():
|
| 451 |
amt = gr.Slider(1, 5000, value=850, label="Amount ($)")
|
|
|
|
| 454 |
"shopping_pos", "entertainment", "food_dining", "health_fitness",
|
| 455 |
"travel", "kids_pets", "home", "personal_care"],
|
| 456 |
value="shopping_net", label="Category")
|
| 457 |
+
hour = gr.Slider(0, 23, value=2, step=1, label="Hour of day (0 = midnight)")
|
| 458 |
+
distance_km = gr.Slider(0, 3000, value=600, step=10,
|
| 459 |
+
label="Distance from cardholder's home (km)")
|
| 460 |
+
with gr.Column():
|
| 461 |
gender = gr.Dropdown(["F", "M"], value="F", label="Gender")
|
| 462 |
state = gr.Dropdown(["NY", "CA", "TX", "FL", "PA", "OH", "IL"],
|
| 463 |
value="NY", label="State")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 464 |
age_years = gr.Slider(18, 90, value=35, step=1, label="Cardholder age")
|
| 465 |
sbtn = gr.Button("Score Transaction", variant="primary")
|
| 466 |
smd = gr.Markdown()
|
| 467 |
splot = gr.Plot()
|
| 468 |
sbtn.click(score_transaction,
|
| 469 |
+
inputs=[amt, category, hour, distance_km, gender, state, age_years],
|
|
|
|
| 470 |
outputs=[smd, splot], scroll_to_output=False)
|
| 471 |
|
| 472 |
with gr.Tab("3. Explainability"):
|