LovnishVerma commited on
Commit
14bb62b
·
verified ·
1 Parent(s): a680ee3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +226 -733
app.py CHANGED
@@ -3,7 +3,7 @@ import pandas as pd
3
  import plotly.express as px
4
  import plotly.graph_objects as go
5
  import numpy as np
6
- from datetime import datetime, timedelta
7
 
8
  # ==========================================
9
  # 1. PAGE CONFIGURATION
@@ -16,300 +16,172 @@ st.set_page_config(
16
  )
17
 
18
  # ==========================================
19
- # 2. ENHANCED PROFESSIONAL STYLING
20
  # ==========================================
21
  st.markdown("""
22
  <style>
23
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');
 
24
 
25
- /* BASE THEME */
26
  .stApp {
27
- background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
28
- color: #1a202c;
29
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
30
  }
31
 
32
- /* HEADER STYLING */
33
- .main-header {
34
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
35
- padding: 2rem;
36
- border-radius: 16px;
37
- box-shadow: 0 20px 60px rgba(102, 126, 234, 0.3);
38
- margin-bottom: 2rem;
39
- color: white;
40
- }
41
-
42
- .main-title {
43
- font-size: 2.5rem;
44
- font-weight: 800;
45
- margin: 0;
46
- letter-spacing: -0.5px;
47
- }
48
-
49
- .main-subtitle {
50
- font-size: 1.1rem;
51
- opacity: 0.95;
52
- margin-top: 0.5rem;
53
- font-weight: 400;
54
- }
55
-
56
- /* METRIC CARDS - ENHANCED */
57
  div[data-testid="stMetric"] {
58
- background: white;
59
- border: none;
60
- border-radius: 12px;
61
- padding: 1.5rem;
62
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
63
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
64
  }
65
-
66
  div[data-testid="stMetric"]:hover {
67
- transform: translateY(-4px);
68
- box-shadow: 0 12px 40px rgba(0, 0, 0, 0.12);
69
  }
70
 
 
71
  div[data-testid="stMetricValue"] {
72
- color: #2d3748 !important;
73
  font-weight: 700 !important;
74
- font-size: 2rem !important;
75
  }
76
-
77
  div[data-testid="stMetricLabel"] {
78
- color: #718096 !important;
79
- font-weight: 500 !important;
80
- font-size: 0.875rem !important;
81
- text-transform: uppercase;
82
- letter-spacing: 0.5px;
83
- }
84
-
85
- div[data-testid="stMetricDelta"] {
86
- font-size: 0.875rem !important;
87
- font-weight: 600 !important;
88
  }
89
 
90
- /* SIDEBAR - MODERN DESIGN */
91
- [data-testid="stSidebar"] {
92
- background: linear-gradient(180deg, #1a202c 0%, #2d3748 100%);
93
- border-right: 1px solid rgba(255, 255, 255, 0.1);
94
- }
95
-
96
- [data-testid="stSidebar"] * {
97
- color: #e2e8f0 !important;
98
- }
99
-
100
- [data-testid="stSidebar"] .stSelectbox label,
101
- [data-testid="stSidebar"] .stMultiSelect label {
102
- color: #cbd5e0 !important;
103
- font-weight: 600 !important;
104
- font-size: 0.875rem !important;
105
- }
106
-
107
- /* SIDEBAR SECTION HEADERS */
108
- [data-testid="stSidebar"] h3 {
109
- color: white !important;
110
- font-weight: 700 !important;
111
- font-size: 1.25rem !important;
112
- margin-top: 1.5rem !important;
113
- }
114
-
115
- /* DATAFRAME STYLING */
116
- div[data-testid="stDataFrame"] {
117
- background: white;
118
- border-radius: 12px;
119
- padding: 1rem;
120
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
121
- }
122
-
123
  div[data-testid="stDataFrame"] div[role="grid"] {
124
- color: #2d3748 !important;
125
  background-color: white !important;
126
- font-size: 0.9rem !important;
127
  }
128
-
129
  div[data-testid="stDataFrame"] div[role="columnheader"] {
130
- color: #1a202c !important;
131
- font-weight: 700 !important;
132
- background-color: #f7fafc !important;
133
- text-transform: uppercase;
134
- font-size: 0.75rem !important;
135
- letter-spacing: 0.5px;
136
- }
137
-
138
- /* TABS STYLING */
139
- .stTabs [data-baseweb="tab-list"] {
140
- gap: 8px;
141
- background-color: white;
142
- border-radius: 12px;
143
- padding: 8px;
144
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
145
- }
146
-
147
- .stTabs [data-baseweb="tab"] {
148
- height: 50px;
149
- border-radius: 8px;
150
- padding: 0 24px;
151
- font-weight: 600;
152
- color: #4a5568;
153
- background-color: transparent;
154
- }
155
-
156
- .stTabs [aria-selected="true"] {
157
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
158
- color: white !important;
159
- }
160
-
161
- /* BUTTONS */
162
- .stButton > button {
163
- border-radius: 8px;
164
- font-weight: 600;
165
- padding: 0.5rem 2rem;
166
- border: none;
167
- transition: all 0.2s ease;
168
- }
169
-
170
- .stButton > button[kind="primary"] {
171
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
172
- }
173
-
174
- .stButton > button:hover {
175
- transform: translateY(-2px);
176
- box-shadow: 0 8px 20px rgba(102, 126, 234, 0.3);
177
  }
178
 
179
- /* CUSTOM COMPONENTS */
180
- .status-badge {
181
- display: inline-flex;
182
- align-items: center;
183
- gap: 8px;
184
- padding: 8px 16px;
185
- border-radius: 24px;
186
- font-size: 0.875rem;
187
- font-weight: 600;
188
- letter-spacing: 0.5px;
189
- }
190
-
191
- .bg-green {
192
- background: linear-gradient(135deg, #d4fc79 0%, #96e6a1 100%);
193
- color: #22543d;
194
- }
195
-
196
- .bg-red {
197
- background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%);
198
- color: #742a2a;
199
- }
200
-
201
- .bg-blue {
202
- background: linear-gradient(135deg, #a1c4fd 0%, #c2e9fb 100%);
203
- color: #2c5282;
204
- }
205
-
206
- .hotspot-card {
207
- background: white;
208
- padding: 16px;
209
- border-radius: 12px;
210
- border-left: 5px solid;
211
- margin-bottom: 12px;
212
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
213
- transition: all 0.2s ease;
214
  }
215
-
216
- .hotspot-card:hover {
217
- transform: translateX(4px);
218
- box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12);
219
  }
220
-
221
- /* INFO BOXES */
222
- .stAlert {
223
- border-radius: 12px;
224
- border: none;
225
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
226
  }
227
 
228
  /* HEADERS */
229
- h1, h2, h3, h4 {
230
- color: #1a202c !important;
231
  font-weight: 700 !important;
232
  }
233
 
234
- /* COLAB LINK BOX */
235
- .colab-box {
236
- background: white;
237
- border-radius: 12px;
238
- padding: 1.5rem;
239
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
240
- border-left: 5px solid #f59e0b;
241
- margin: 1rem 0;
242
- }
243
-
244
- .colab-link {
245
  display: inline-flex;
246
  align-items: center;
247
- gap: 8px;
248
- padding: 10px 20px;
249
- background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
250
- color: white !important;
251
- text-decoration: none;
252
- border-radius: 8px;
253
  font-weight: 600;
254
- transition: all 0.2s ease;
255
  }
 
 
256
 
257
- .colab-link:hover {
258
- transform: translateY(-2px);
259
- box-shadow: 0 8px 20px rgba(245, 158, 11, 0.3);
260
  }
261
  </style>
262
  """, unsafe_allow_html=True)
263
 
264
  # ==========================================
265
- # 3. DATA LOADING
266
  # ==========================================
267
  @st.cache_data
268
  def load_data():
 
269
  try:
270
  df = pd.read_csv('analyzed_aadhaar_data.csv')
271
  except FileNotFoundError:
272
- dates = pd.date_range(start="2025-01-01", periods=150)
 
273
  df = pd.DataFrame({
274
  'date': dates,
275
- 'state': np.random.choice(['Maharashtra', 'Uttar Pradesh', 'Bihar', 'Karnataka', 'Delhi', 'West Bengal', 'Tamil Nadu'], 150),
276
- 'district': np.random.choice(['District A', 'District B', 'District C', 'District D', 'District E'], 150),
277
- 'pincode': np.random.randint(110001, 800000, 150),
278
- 'RISK_SCORE': np.random.uniform(15, 98, 150),
279
- 'total_activity': np.random.randint(50, 800, 150),
280
- 'enrol_adult': np.random.randint(10, 400, 150),
281
- 'ratio_deviation': np.random.uniform(-0.15, 0.6, 150),
282
- 'is_weekend': np.random.choice([0, 1], 150, p=[0.7, 0.3])
283
  })
284
 
 
285
  if 'date' in df.columns:
286
  df['date'] = pd.to_datetime(df['date'])
287
 
288
- # Geographic coordinates for India - State-based realistic placement
289
- np.random.seed(42)
290
- state_coords = {
291
- 'Maharashtra': {'lat': (15.5, 22.0), 'lon': (72.5, 80.5)},
292
- 'Uttar Pradesh': {'lat': (23.5, 30.5), 'lon': (77.0, 84.5)},
293
- 'Bihar': {'lat': (24.0, 27.5), 'lon': (83.5, 88.5)},
294
- 'Karnataka': {'lat': (11.5, 18.5), 'lon': (74.0, 78.5)},
295
- 'Delhi': {'lat': (28.4, 28.9), 'lon': (76.8, 77.5)},
296
- 'West Bengal': {'lat': (21.5, 27.5), 'lon': (85.5, 89.5)},
297
- 'Tamil Nadu': {'lat': (8.0, 13.5), 'lon': (76.5, 80.5)}
 
 
 
 
 
 
 
298
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
299
 
300
- def assign_coords(row):
301
- state = row['state']
302
- if state in state_coords:
303
- coords = state_coords[state]
304
- return pd.Series({
305
- 'lat': np.random.uniform(coords['lat'][0], coords['lat'][1]),
306
- 'lon': np.random.uniform(coords['lon'][0], coords['lon'][1])
307
- })
308
- else:
309
- return pd.Series({'lat': np.random.uniform(8.5, 32.0), 'lon': np.random.uniform(70.0, 88.0)})
310
-
311
- df[['lat', 'lon']] = df.apply(assign_coords, axis=1)
312
 
 
313
  df['risk_category'] = pd.cut(
314
  df['RISK_SCORE'],
315
  bins=[-1, 50, 75, 85, 100],
@@ -318,20 +190,21 @@ def load_data():
318
 
319
  return df
320
 
 
321
  df = load_data()
322
 
323
  # ==========================================
324
- # 4. SIDEBAR
325
  # ==========================================
326
  with st.sidebar:
327
- st.markdown("### 🛡️ SENTINEL CONTROL CENTER")
328
  st.markdown("---")
329
 
330
- # Filters
331
- st.markdown("#### 📍 Geographic Filters")
332
  state_list = ['All'] + sorted(df['state'].unique().tolist())
333
- selected_state = st.selectbox("State", state_list, key="state_filter")
334
 
 
335
  if selected_state != 'All':
336
  filtered_df = df[df['state'] == selected_state]
337
  district_list = ['All'] + sorted(filtered_df['district'].unique().tolist())
@@ -339,16 +212,16 @@ with st.sidebar:
339
  filtered_df = df.copy()
340
  district_list = ['All']
341
 
342
- selected_district = st.selectbox("District", district_list, key="district_filter")
343
 
344
  if selected_district != 'All':
345
  filtered_df = filtered_df[filtered_df['district'] == selected_district]
346
-
347
  st.markdown("---")
348
- st.markdown("#### 🚨 Risk Filters")
349
 
 
350
  risk_filter = st.multiselect(
351
- "Risk Levels",
352
  options=['Low', 'Medium', 'High', 'Critical'],
353
  default=['High', 'Critical']
354
  )
@@ -357,544 +230,164 @@ with st.sidebar:
357
  filtered_df = filtered_df[filtered_df['risk_category'].isin(risk_filter)]
358
 
359
  st.markdown("---")
360
- st.markdown("#### ⚙️ Advanced Options")
361
-
362
- show_weekend = st.checkbox("Show Weekend Activity Only", value=False)
363
- if show_weekend:
364
- filtered_df = filtered_df[filtered_df['is_weekend'] == 1]
365
-
366
- min_activity = st.slider("Min. Transaction Volume", 0, 800, 0)
367
- filtered_df = filtered_df[filtered_df['total_activity'] >= min_activity]
368
-
369
- st.markdown("---")
370
- st.info("**👤 User:** UIDAI_Officer\n\n**🏢 Team:** UIDAI_4571\n\n**📅 Session:** " + datetime.now().strftime("%d %b %Y"))
371
 
372
  # ==========================================
373
- # 5. HEADER
374
  # ==========================================
375
- st.markdown("""
376
- <div class="main-header">
377
- <div style="display: flex; justify-content: space-between; align-items: center;">
378
- <div>
379
- <div class="main-title">🛡️ PROJECT SENTINEL</div>
380
- <div class="main-subtitle">AI-Powered Fraud Detection & Risk Analysis System</div>
381
- </div>
382
- <div style="text-align: right;">
383
- <span class="status-badge bg-green">● SYSTEM ACTIVE</span>
384
- <div style="font-size: 0.875rem; opacity: 0.9; margin-top: 8px;">
385
- Last Sync: Just now
386
- </div>
387
- </div>
388
- </div>
389
- </div>
390
- """, unsafe_allow_html=True)
391
 
392
- # ==========================================
393
- # 6. COLAB INTEGRATION SECTION
394
- # ==========================================
395
- st.markdown("""
396
- <div class="colab-box">
397
- <div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 1rem;">
398
- <div>
399
- <h4 style="margin: 0; color: #1a202c;">📊 Model Training & Analysis</h4>
400
- <p style="margin: 0.5rem 0 0 0; color: #4a5568; font-size: 0.9rem;">
401
- Access the full ML pipeline, model training, and detailed analytics in Google Colab
402
- </p>
403
- </div>
404
- <div>
405
- <a href="https://colab.research.google.com/drive/1YAQ4nfxltvG_cts3fmGc_zi2JQc4oPOT?usp=sharing"
406
- target="_blank"
407
- class="colab-link">
408
- <svg width="20" height="20" viewBox="0 0 24 24" fill="white">
409
- <path d="M12 0C5.373 0 0 5.373 0 12s5.373 12 12 12 12-5.373 12-12S18.627 0 12 0zm0 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10z"/>
410
- <path d="M12 6c-3.309 0-6 2.691-6 6s2.691 6 6 6 6-2.691 6-6-2.691-6-6-6zm0 10c-2.206 0-4-1.794-4-4s1.794-4 4-4 4 1.794 4 4-1.794 4-4 4z"/>
411
- </svg>
412
- Open in Colab
413
- </a>
414
- </div>
415
  </div>
416
- </div>
417
- """, unsafe_allow_html=True)
418
 
419
- st.markdown("##")
420
 
421
- # ==========================================
422
- # 7. KPI METRICS
423
- # ==========================================
424
  total_centers = len(filtered_df)
425
  high_risk = len(filtered_df[filtered_df['RISK_SCORE'] > 75])
426
- critical_risk = len(filtered_df[filtered_df['RISK_SCORE'] > 85])
427
  avg_risk = filtered_df['RISK_SCORE'].mean() if not filtered_df.empty else 0
428
  weekend_alerts = len(filtered_df[(filtered_df['is_weekend'] == 1) & (filtered_df['RISK_SCORE'] > 70)])
429
 
430
- col1, col2, col3, col4, col5 = st.columns(5)
431
-
432
- with col1:
433
- st.metric("Centers Analyzed", f"{total_centers:,}", border=True)
434
-
435
- with col2:
436
- st.metric("High Risk", f"{high_risk}",
437
- delta=f"{(high_risk/total_centers*100):.1f}%" if total_centers > 0 else "0%",
438
- delta_color="inverse", border=True)
439
-
440
- with col3:
441
- st.metric("Critical Cases", f"{critical_risk}",
442
- delta="Immediate Action",
443
- delta_color="inverse", border=True)
444
-
445
- with col4:
446
- st.metric("Avg Risk Score", f"{avg_risk:.1f}",
447
- delta="out of 100",
448
- delta_color="off", border=True)
449
-
450
- with col5:
451
- st.metric("Weekend Anomalies", f"{weekend_alerts}",
452
- delta="Suspicious",
453
- delta_color="inverse", border=True)
454
 
455
- st.markdown("##")
456
 
457
  # ==========================================
458
- # 8. MAIN TABS
459
  # ==========================================
460
- tab1, tab2, tab3, tab4 = st.tabs([
461
- "🗺️ Geographic Risk Map",
462
- "📋 Priority Investigation List",
463
- "📊 Pattern Analytics",
464
- "📈 Trend Analysis"
465
- ])
466
 
467
- # --- TAB 1: MAP ---
468
- with tab1:
469
- col_map, col_sidebar = st.columns([3, 1])
470
 
471
  with col_map:
472
  if not filtered_df.empty:
473
- # Prepare hover template data
474
- hover_text = []
475
- for idx, row in filtered_df.iterrows():
476
- text = f"<b>{row['district']}, {row['state']}</b><br>"
477
- text += f"PIN: {row['pincode']}<br>"
478
- text += f"Risk Score: {row['RISK_SCORE']:.1f}/100<br>"
479
- text += f"Activity Volume: {row['total_activity']}<br>"
480
- text += f"Adult Enrollments: {row['enrol_adult']}<br>"
481
- text += f"Date: {row['date'].strftime('%d %b %Y')}"
482
- hover_text.append(text)
483
-
484
- filtered_df['hover_info'] = hover_text
485
-
486
- # Create map with better visualization
487
  fig_map = px.scatter_mapbox(
488
  filtered_df,
489
  lat="lat",
490
  lon="lon",
491
  color="RISK_SCORE",
492
  size="total_activity",
493
- color_continuous_scale=["#10b981", "#fbbf24", "#f97316", "#ef4444"],
494
- size_max=35,
495
- zoom=4.5,
496
- center={"lat": 20.5937, "lon": 78.9629},
497
- custom_data=['hover_info'],
498
- mapbox_style="open-street-map",
499
- height=650,
500
- )
501
-
502
- # Update hover template
503
- fig_map.update_traces(
504
- hovertemplate='%{customdata[0]}<extra></extra>',
505
- marker=dict(opacity=0.8)
506
- )
507
-
508
- fig_map.update_layout(
509
- margin={"r":0,"t":10,"l":0,"b":0},
510
- coloraxis_colorbar=dict(
511
- title=dict(text="<b>Risk Score</b>", font=dict(size=13, family="Inter")),
512
- thickness=20,
513
- len=0.7,
514
- tickmode='linear',
515
- tick0=0,
516
- dtick=20,
517
- tickfont=dict(size=11, family="Inter")
518
- ),
519
- font=dict(family="Inter", size=12),
520
- hoverlabel=dict(
521
- bgcolor="white",
522
- font_size=13,
523
- font_family="Inter"
524
- )
525
  )
526
-
527
  st.plotly_chart(fig_map, use_container_width=True)
528
-
529
- # Add map legend
530
- st.markdown("""
531
- <div style="background: white; padding: 15px; border-radius: 8px; margin-top: 10px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
532
- <div style="display: flex; justify-content: space-around; align-items: center; flex-wrap: wrap; gap: 15px;">
533
- <div style="display: flex; align-items: center; gap: 8px;">
534
- <div style="width: 20px; height: 20px; border-radius: 50%; background: #10b981;"></div>
535
- <span style="font-size: 0.85rem; color: #4a5568;"><b>Low Risk</b> (0-50)</span>
536
- </div>
537
- <div style="display: flex; align-items: center; gap: 8px;">
538
- <div style="width: 20px; height: 20px; border-radius: 50%; background: #fbbf24;"></div>
539
- <span style="font-size: 0.85rem; color: #4a5568;"><b>Medium Risk</b> (50-75)</span>
540
- </div>
541
- <div style="display: flex; align-items: center; gap: 8px;">
542
- <div style="width: 20px; height: 20px; border-radius: 50%; background: #f97316;"></div>
543
- <span style="font-size: 0.85rem; color: #4a5568;"><b>High Risk</b> (75-85)</span>
544
- </div>
545
- <div style="display: flex; align-items: center; gap: 8px;">
546
- <div style="width: 20px; height: 20px; border-radius: 50%; background: #ef4444;"></div>
547
- <span style="font-size: 0.85rem; color: #4a5568;"><b>Critical</b> (85-100)</span>
548
- </div>
549
- <div style="display: flex; align-items: center; gap: 8px;">
550
- <div style="font-size: 0.85rem; color: #718096;">
551
- <i>Bubble size = Transaction volume</i>
552
- </div>
553
- </div>
554
- </div>
555
- </div>
556
- """, unsafe_allow_html=True)
557
  else:
558
- st.warning("⚠️ No data matches current filters. Please adjust your selection.")
559
-
560
- with col_sidebar:
561
- st.markdown("### 🎯 Top Risk Hotspots")
562
- st.markdown("---")
563
-
564
  if not filtered_df.empty:
565
- top_districts = filtered_df.groupby('district').agg({
566
- 'RISK_SCORE': 'mean',
567
- 'total_activity': 'sum'
568
- }).sort_values('RISK_SCORE', ascending=False).head(8)
569
-
570
- for idx, (district, row) in enumerate(top_districts.iterrows(), 1):
571
- score = row['RISK_SCORE']
572
- volume = row['total_activity']
573
-
574
- if score > 85:
575
- color = "#ef4444"
576
- label = "CRITICAL"
577
- elif score > 75:
578
- color = "#f59e0b"
579
- label = "HIGH"
580
- else:
581
- color = "#10b981"
582
- label = "MEDIUM"
583
-
584
  st.markdown(f"""
585
- <div class="hotspot-card" style="border-left-color: {color};">
586
- <div style="display: flex; justify-content: space-between; align-items: center;">
587
- <div style="font-weight: 700; color: #1a202c; font-size: 0.95rem;">
588
- #{idx} {district}
589
- </div>
590
- <span style="background: {color}; color: white; padding: 2px 8px; border-radius: 4px; font-size: 0.7rem; font-weight: 700;">
591
- {label}
592
- </span>
593
- </div>
594
- <div style="display: flex; justify-content: space-between; margin-top: 8px; font-size: 0.85rem;">
595
- <span style="color: #718096;">Risk: <b style="color: #2d3748;">{score:.1f}</b></span>
596
- <span style="color: #718096;">Vol: <b style="color: #2d3748;">{int(volume)}</b></span>
597
- </div>
598
  </div>
599
  """, unsafe_allow_html=True)
600
 
601
- # --- TAB 2: PRIORITY LIST ---
602
- with tab2:
603
- col_header, col_export = st.columns([3, 1])
604
-
605
- with col_header:
606
- st.markdown("### 🎯 Investigation Priority Queue")
607
- st.caption("Sorted by risk score - Immediate action required for scores > 85")
608
-
609
- with col_export:
610
- target_list = filtered_df[filtered_df['RISK_SCORE'] > 75].sort_values('RISK_SCORE', ascending=False)
611
-
612
- st.download_button(
613
- "📥 Export Report",
614
- data=target_list.to_csv(index=False),
615
- file_name=f"sentinel_report_{datetime.now().strftime('%Y%m%d_%H%M')}.csv",
616
- mime="text/csv",
617
- type="primary",
618
- use_container_width=True
619
- )
620
-
621
- st.markdown("##")
 
 
 
 
622
 
623
- if not target_list.empty:
624
- st.dataframe(
625
- target_list[['date', 'state', 'district', 'pincode', 'enrol_adult', 'total_activity', 'ratio_deviation', 'RISK_SCORE']],
626
- column_config={
627
- "RISK_SCORE": st.column_config.ProgressColumn(
628
- "Risk Score",
629
- help="AI-calculated fraud probability",
630
- format="%.1f%%",
631
- min_value=0,
632
- max_value=100,
633
- ),
634
- "date": st.column_config.DateColumn("Date", format="DD MMM YYYY"),
635
- "total_activity": st.column_config.NumberColumn("Volume", format="%d"),
636
- "enrol_adult": st.column_config.NumberColumn("Adult Enrollments", format="%d"),
637
- "ratio_deviation": st.column_config.NumberColumn("Deviation", format="%.3f"),
638
- "state": "State",
639
- "district": "District",
640
- "pincode": st.column_config.NumberColumn("PIN Code", format="%d")
641
- },
642
- use_container_width=True,
643
- hide_index=True,
644
- height=500
645
- )
646
-
647
- st.info(f"📊 **{len(target_list)}** centers flagged for investigation | **{len(target_list[target_list['RISK_SCORE'] > 85])}** critical priority")
648
- else:
649
- st.success("✅ No high-risk centers found with current filters!")
650
 
651
- # --- TAB 3: ANALYTICS ---
652
- with tab3:
653
- col1, col2 = st.columns(2)
654
 
655
- with col1:
656
- st.markdown("### 🔍 Ghost ID Pattern Analysis")
657
-
658
  fig_scatter = px.scatter(
659
  filtered_df,
660
  x="total_activity",
661
  y="ratio_deviation",
662
  color="risk_category",
663
- size="RISK_SCORE",
664
- color_discrete_map={
665
- 'Critical': '#dc2626',
666
- 'High': '#ea580c',
667
- 'Medium': '#ca8a04',
668
- 'Low': '#16a34a'
669
- },
670
- labels={
671
- "ratio_deviation": "Deviation from Baseline",
672
- "total_activity": "Daily Transactions"
673
- },
674
- hover_data=['pincode', 'district', 'state'],
675
- height=400
676
  )
677
-
678
- fig_scatter.add_hline(
679
- y=0.2,
680
- line_dash="dash",
681
- line_color="red",
682
- annotation_text="Fraud Threshold (0.2)",
683
- annotation_position="right"
684
- )
685
-
686
- fig_scatter.update_layout(
687
- font=dict(family="Inter"),
688
- plot_bgcolor='rgba(0,0,0,0)',
689
- paper_bgcolor='rgba(0,0,0,0)'
690
- )
691
-
692
  st.plotly_chart(fig_scatter, use_container_width=True)
693
-
694
- with col2:
695
- st.markdown("### 📊 Risk Distribution")
696
-
697
  fig_hist = px.histogram(
698
- filtered_df,
699
- x="RISK_SCORE",
700
- nbins=25,
701
- color_discrete_sequence=['#8b5cf6'],
702
- labels={"RISK_SCORE": "Risk Score"},
703
- height=400
704
- )
705
-
706
- fig_hist.update_layout(
707
- bargap=0.05,
708
- font=dict(family="Inter"),
709
- plot_bgcolor='rgba(0,0,0,0)',
710
- paper_bgcolor='rgba(0,0,0,0)',
711
- showlegend=False
712
  )
713
-
714
  st.plotly_chart(fig_hist, use_container_width=True)
715
-
716
- st.markdown("##")
717
-
718
- col3, col4 = st.columns(2)
719
-
720
- with col3:
721
- st.markdown("### 🏛️ State-wise Risk Summary")
722
-
723
- state_summary = filtered_df.groupby('state').agg({
724
- 'RISK_SCORE': 'mean',
725
- 'total_activity': 'sum'
726
- }).sort_values('RISK_SCORE', ascending=False).reset_index()
727
-
728
- fig_bar = px.bar(
729
- state_summary,
730
- x='state',
731
- y='RISK_SCORE',
732
- color='RISK_SCORE',
733
- color_continuous_scale='RdYlGn_r',
734
- labels={'RISK_SCORE': 'Avg Risk Score', 'state': 'State'},
735
- height=400
736
- )
737
-
738
- fig_bar.update_layout(
739
- font=dict(family="Inter"),
740
- plot_bgcolor='rgba(0,0,0,0)',
741
- paper_bgcolor='rgba(0,0,0,0)',
742
- showlegend=False
743
- )
744
-
745
- st.plotly_chart(fig_bar, use_container_width=True)
746
-
747
- with col4:
748
- st.markdown("### 📈 Risk Category Breakdown")
749
-
750
- risk_counts = filtered_df['risk_category'].value_counts()
751
-
752
- fig_pie = px.pie(
753
- values=risk_counts.values,
754
- names=risk_counts.index,
755
- color=risk_counts.index,
756
- color_discrete_map={
757
- 'Critical': '#dc2626',
758
- 'High': '#ea580c',
759
- 'Medium': '#ca8a04',
760
- 'Low': '#16a34a'
761
- },
762
- height=400
763
- )
764
-
765
- fig_pie.update_traces(
766
- textposition='inside',
767
- textinfo='percent+label',
768
- hovertemplate='<b>%{label}</b><br>Count: %{value}<br>Percentage: %{percent}<extra></extra>'
769
- )
770
-
771
- fig_pie.update_layout(
772
- font=dict(family="Inter"),
773
- showlegend=True,
774
- legend=dict(orientation="h", yanchor="bottom", y=-0.2, xanchor="center", x=0.5)
775
- )
776
-
777
- st.plotly_chart(fig_pie, use_container_width=True)
778
-
779
- # --- TAB 4: TRENDS ---
780
- with tab4:
781
- st.markdown("### 📈 Temporal Analysis")
782
-
783
- if 'date' in filtered_df.columns and not filtered_df.empty:
784
- # Time series of risk scores
785
- daily_risk = filtered_df.groupby('date').agg({
786
- 'RISK_SCORE': 'mean',
787
- 'total_activity': 'sum'
788
- }).reset_index()
789
-
790
- fig_trend = go.Figure()
791
-
792
- fig_trend.add_trace(go.Scatter(
793
- x=daily_risk['date'],
794
- y=daily_risk['RISK_SCORE'],
795
- mode='lines+markers',
796
- name='Avg Risk Score',
797
- line=dict(color='#8b5cf6', width=3),
798
- marker=dict(size=6),
799
- fill='tozeroy',
800
- fillcolor='rgba(139, 92, 246, 0.1)'
801
- ))
802
-
803
- fig_trend.update_layout(
804
- height=400,
805
- font=dict(family="Inter"),
806
- plot_bgcolor='rgba(0,0,0,0)',
807
- paper_bgcolor='rgba(0,0,0,0)',
808
- hovermode='x unified',
809
- xaxis_title="Date",
810
- yaxis_title="Average Risk Score"
811
- )
812
-
813
- st.plotly_chart(fig_trend, use_container_width=True)
814
-
815
- st.markdown("##")
816
-
817
- col1, col2 = st.columns(2)
818
-
819
- with col1:
820
- st.markdown("### 📅 Weekend vs Weekday Risk")
821
-
822
- weekend_comparison = filtered_df.groupby('is_weekend').agg({
823
- 'RISK_SCORE': 'mean',
824
- 'total_activity': 'sum'
825
- }).reset_index()
826
-
827
- weekend_comparison['day_type'] = weekend_comparison['is_weekend'].map({
828
- 0: 'Weekday',
829
- 1: 'Weekend'
830
- })
831
-
832
- fig_weekend = px.bar(
833
- weekend_comparison,
834
- x='day_type',
835
- y='RISK_SCORE',
836
- color='day_type',
837
- color_discrete_map={'Weekday': '#3b82f6', 'Weekend': '#ef4444'},
838
- labels={'RISK_SCORE': 'Average Risk Score', 'day_type': ''},
839
- height=400
840
- )
841
-
842
- fig_weekend.update_layout(
843
- font=dict(family="Inter"),
844
- plot_bgcolor='rgba(0,0,0,0)',
845
- paper_bgcolor='rgba(0,0,0,0)',
846
- showlegend=False
847
- )
848
-
849
- st.plotly_chart(fig_weekend, use_container_width=True)
850
-
851
- with col2:
852
- st.markdown("### 📊 Activity Volume Trends")
853
-
854
- fig_activity = go.Figure()
855
-
856
- fig_activity.add_trace(go.Bar(
857
- x=daily_risk['date'],
858
- y=daily_risk['total_activity'],
859
- name='Total Activity',
860
- marker_color='#06b6d4'
861
- ))
862
-
863
- fig_activity.update_layout(
864
- height=400,
865
- font=dict(family="Inter"),
866
- plot_bgcolor='rgba(0,0,0,0)',
867
- paper_bgcolor='rgba(0,0,0,0)',
868
- xaxis_title="Date",
869
- yaxis_title="Total Transactions",
870
- showlegend=False
871
- )
872
-
873
- st.plotly_chart(fig_activity, use_container_width=True)
874
- else:
875
- st.warning("⚠️ No temporal data available for analysis.")
876
 
877
  # ==========================================
878
- # 9. FOOTER
879
  # ==========================================
880
  st.markdown("---")
881
  st.markdown("""
882
- <div style="background: white; padding: 1.5rem; border-radius: 12px; margin-top: 2rem; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);">
883
- <div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 1rem;">
884
- <div style="text-align: left;">
885
- <div style="font-weight: 700; color: #1a202c; font-size: 1.1rem;">🛡️ Project Sentinel</div>
886
- <div style="color: #718096; font-size: 0.875rem; margin-top: 4px;">
887
- AI-Powered Fraud Detection System
888
- </div>
889
- </div>
890
- <div style="text-align: center; color: #4a5568; font-size: 0.85rem;">
891
- <div><b>UIDAI Hackathon 2026</b></div>
892
- <div style="margin-top: 4px;">Team UIDAI_4571</div>
893
- </div>
894
- <div style="text-align: right; color: #94a3b8; font-size: 0.8rem; font-style: italic;">
895
- <div>Confidential</div>
896
- <div>For Official Use Only</div>
897
- </div>
898
- </div>
899
  </div>
900
  """, unsafe_allow_html=True)
 
3
  import plotly.express as px
4
  import plotly.graph_objects as go
5
  import numpy as np
6
+ from datetime import datetime
7
 
8
  # ==========================================
9
  # 1. PAGE CONFIGURATION
 
16
  )
17
 
18
  # ==========================================
19
+ # 2. PROFESSIONAL STYLING (THEME OVERRIDE)
20
  # ==========================================
21
  st.markdown("""
22
  <style>
23
+ /* IMPORT FONTS */
24
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
25
 
26
+ /* FORCE LIGHT THEME BACKGROUNDS & TEXT */
27
  .stApp {
28
+ background-color: #f8fafc; /* Light Blue-Grey */
29
+ color: #0f172a; /* Slate 900 */
30
+ font-family: 'Inter', sans-serif;
31
  }
32
 
33
+ /* METRIC CARDS - GLASSMORPHISM */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  div[data-testid="stMetric"] {
35
+ background-color: #ffffff;
36
+ border: 1px solid #e2e8f0;
37
+ border-radius: 8px;
38
+ padding: 15px;
39
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
40
+ transition: transform 0.2s;
41
  }
 
42
  div[data-testid="stMetric"]:hover {
43
+ transform: translateY(-2px);
44
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
45
  }
46
 
47
+ /* FORCE DARK TEXT FOR METRICS (Fixes White-on-White) */
48
  div[data-testid="stMetricValue"] {
49
+ color: #0f172a !important;
50
  font-weight: 700 !important;
 
51
  }
 
52
  div[data-testid="stMetricLabel"] {
53
+ color: #64748b !important; /* Slate 500 */
 
 
 
 
 
 
 
 
 
54
  }
55
 
56
+ /* DATAFRAME STYLING (Fixes White-on-White) */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  div[data-testid="stDataFrame"] div[role="grid"] {
58
+ color: #334155 !important; /* Slate 700 */
59
  background-color: white !important;
 
60
  }
 
61
  div[data-testid="stDataFrame"] div[role="columnheader"] {
62
+ color: #0f172a !important;
63
+ font-weight: 600 !important;
64
+ background-color: #f1f5f9 !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  }
66
 
67
+ /* SIDEBAR STYLING */
68
+ [data-testid="stSidebar"] {
69
+ background-color: #1e293b; /* Slate 800 */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  }
71
+ [data-testid="stSidebar"] * {
72
+ color: #f8fafc !important; /* Light text for sidebar */
 
 
73
  }
74
+ [data-testid="stSidebar"] .stSelectbox label,
75
+ [data-testid="stSidebar"] .stMultiSelect label {
76
+ color: #94a3b8 !important;
 
 
 
77
  }
78
 
79
  /* HEADERS */
80
+ h1, h2, h3 {
81
+ color: #0f172a !important;
82
  font-weight: 700 !important;
83
  }
84
 
85
+ /* CUSTOM BADGES */
86
+ .status-badge {
 
 
 
 
 
 
 
 
 
87
  display: inline-flex;
88
  align-items: center;
89
+ padding: 4px 12px;
90
+ border-radius: 9999px;
91
+ font-size: 12px;
 
 
 
92
  font-weight: 600;
 
93
  }
94
+ .bg-red { background-color: #fee2e2; color: #991b1b; }
95
+ .bg-green { background-color: #dcfce7; color: #166534; }
96
 
97
+ /* MAP CANVAS FIX */
98
+ .js-plotly-plot .plotly .main-svg {
99
+ background-color: rgba(0,0,0,0) !important;
100
  }
101
  </style>
102
  """, unsafe_allow_html=True)
103
 
104
  # ==========================================
105
+ # 3. SMART DATA LOADING (FIXED MAPPING)
106
  # ==========================================
107
  @st.cache_data
108
  def load_data():
109
+ # 1. Load or Generate Data
110
  try:
111
  df = pd.read_csv('analyzed_aadhaar_data.csv')
112
  except FileNotFoundError:
113
+ # Dummy Data Generator if file missing
114
+ dates = pd.date_range(start="2025-01-01", periods=200)
115
  df = pd.DataFrame({
116
  'date': dates,
117
+ 'state': np.random.choice(['Maharashtra', 'Uttar Pradesh', 'Bihar', 'Karnataka', 'Delhi', 'West Bengal', 'Tamil Nadu'], 200),
118
+ 'district': np.random.choice(['North', 'South', 'East', 'West', 'Central', 'Rural A', 'Urban B'], 200),
119
+ 'pincode': np.random.randint(110001, 800000, 200),
120
+ 'RISK_SCORE': np.random.uniform(15, 99, 200),
121
+ 'total_activity': np.random.randint(50, 800, 200),
122
+ 'enrol_adult': np.random.randint(10, 400, 200),
123
+ 'ratio_deviation': np.random.uniform(-0.15, 0.6, 200),
124
+ 'is_weekend': np.random.choice([0, 1], 200, p=[0.7, 0.3])
125
  })
126
 
127
+ # Standardize Date
128
  if 'date' in df.columns:
129
  df['date'] = pd.to_datetime(df['date'])
130
 
131
+ # ---------------------------------------------------------
132
+ # SMART GEO-CLUSTERING LOGIC (THE FIX)
133
+ # ---------------------------------------------------------
134
+ # Define approximate center points for major states
135
+ state_centers = {
136
+ 'Maharashtra': (19.7515, 75.7139),
137
+ 'Uttar Pradesh': (26.8467, 80.9462),
138
+ 'Bihar': (25.0961, 85.3131),
139
+ 'Karnataka': (15.3173, 75.7139),
140
+ 'Delhi': (28.7041, 77.1025),
141
+ 'West Bengal': (22.9868, 87.8550),
142
+ 'Tamil Nadu': (11.1271, 78.6569),
143
+ 'Kerala': (10.8505, 76.2711),
144
+ 'Gujarat': (22.2587, 71.1924),
145
+ 'Rajasthan': (27.0238, 74.2179),
146
+ 'Assam': (26.2006, 92.9376),
147
+ 'Meghalaya': (25.4670, 91.3662)
148
  }
149
+
150
+ def get_coords(row):
151
+ state = row.get('state', 'Delhi')
152
+ district = str(row.get('district', 'Unknown'))
153
+
154
+ # 1. Get State Base Coordinates
155
+ base_lat, base_lon = state_centers.get(state, (20.5937, 78.9629)) # Default to India Center
156
+
157
+ # 2. DETERMINISTIC HASHING FOR DISTRICT
158
+ # This ensures "District A" is ALWAYS in the same spot relative to the State Center
159
+ # Creates distinct clusters instead of random noise
160
+ district_hash = hash(state + district)
161
+ np.random.seed(district_hash % 2**32)
162
+
163
+ # Offset the district center by up to 1.5 degrees (~150km) from state center
164
+ dist_lat_offset = np.random.uniform(-1.5, 1.5)
165
+ dist_lon_offset = np.random.uniform(-1.5, 1.5)
166
+
167
+ # 3. INDIVIDUAL CENTER JITTER
168
+ # Add tiny random noise (~4km) so points don't stack perfectly
169
+ # We re-seed with None to get true randomness for the jitter
170
+ np.random.seed(None)
171
+ noise_lat = np.random.normal(0, 0.04)
172
+ noise_lon = np.random.normal(0, 0.04)
173
+
174
+ return pd.Series({
175
+ 'lat': base_lat + dist_lat_offset + noise_lat,
176
+ 'lon': base_lon + dist_lon_offset + noise_lon
177
+ })
178
 
179
+ # Apply coordinates
180
+ coords = df.apply(get_coords, axis=1)
181
+ df['lat'] = coords['lat']
182
+ df['lon'] = coords['lon']
 
 
 
 
 
 
 
 
183
 
184
+ # Risk Categories
185
  df['risk_category'] = pd.cut(
186
  df['RISK_SCORE'],
187
  bins=[-1, 50, 75, 85, 100],
 
190
 
191
  return df
192
 
193
+ # Load Data
194
  df = load_data()
195
 
196
  # ==========================================
197
+ # 4. SIDEBAR & FILTERS
198
  # ==========================================
199
  with st.sidebar:
200
+ st.markdown("### 🛡️ Sentinel Control")
201
  st.markdown("---")
202
 
203
+ # State Filter
 
204
  state_list = ['All'] + sorted(df['state'].unique().tolist())
205
+ selected_state = st.selectbox("📍 Select State", state_list)
206
 
207
+ # District Filter
208
  if selected_state != 'All':
209
  filtered_df = df[df['state'] == selected_state]
210
  district_list = ['All'] + sorted(filtered_df['district'].unique().tolist())
 
212
  filtered_df = df.copy()
213
  district_list = ['All']
214
 
215
+ selected_district = st.selectbox("🏙️ Select District", district_list)
216
 
217
  if selected_district != 'All':
218
  filtered_df = filtered_df[filtered_df['district'] == selected_district]
219
+
220
  st.markdown("---")
 
221
 
222
+ # Risk Filter
223
  risk_filter = st.multiselect(
224
+ "🚨 Risk Level",
225
  options=['Low', 'Medium', 'High', 'Critical'],
226
  default=['High', 'Critical']
227
  )
 
230
  filtered_df = filtered_df[filtered_df['risk_category'].isin(risk_filter)]
231
 
232
  st.markdown("---")
233
+ st.info(f"**User:** UIDAI_Officer\n\n**Team:** UIDAI_4571")
 
 
 
 
 
 
 
 
 
 
234
 
235
  # ==========================================
236
+ # 5. HEADER & KPI METRICS
237
  # ==========================================
238
+ col1, col2 = st.columns([3, 1])
239
+ with col1:
240
+ st.title("Project Sentinel Dashboard")
241
+ st.markdown("Context-Aware Fraud Detection System")
 
 
 
 
 
 
 
 
 
 
 
 
242
 
243
+ with col2:
244
+ st.markdown("""
245
+ <div style="text-align: right; padding-top: 20px;">
246
+ <span class="status-badge bg-green">● System Online</span>
247
+ <div style="font-size: 12px; color: #64748b; margin-top: 5px;">Live Monitor</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
248
  </div>
249
+ """, unsafe_allow_html=True)
 
250
 
251
+ st.markdown("---")
252
 
253
+ # METRICS ROW
254
+ m1, m2, m3, m4 = st.columns(4)
 
255
  total_centers = len(filtered_df)
256
  high_risk = len(filtered_df[filtered_df['RISK_SCORE'] > 75])
 
257
  avg_risk = filtered_df['RISK_SCORE'].mean() if not filtered_df.empty else 0
258
  weekend_alerts = len(filtered_df[(filtered_df['is_weekend'] == 1) & (filtered_df['RISK_SCORE'] > 70)])
259
 
260
+ m1.metric("Total Centers", f"{total_centers:,}", border=True)
261
+ m2.metric("High Risk Alerts", f"{high_risk}", delta="Action Required", delta_color="inverse", border=True)
262
+ m3.metric("Avg. Risk Score", f"{avg_risk:.1f}/100", border=True)
263
+ m4.metric("Weekend Spikes", f"{weekend_alerts}", "Unauthorized", delta_color="off", border=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
264
 
265
+ st.markdown("##") # Spacer
266
 
267
  # ==========================================
268
+ # 6. MAIN TABS
269
  # ==========================================
270
+ tab_map, tab_list, tab_charts = st.tabs(["🗺️ Geographic Risk", "📋 Priority List", "📊 Pattern Analytics"])
 
 
 
 
 
271
 
272
+ # --- TAB 1: GEOGRAPHIC RISK (FIXED MAP) ---
273
+ with tab_map:
274
+ col_map, col_details = st.columns([3, 1])
275
 
276
  with col_map:
277
  if not filtered_df.empty:
278
+ # Using Open-Street-Map for better contrast and no-token requirement
 
 
 
 
 
 
 
 
 
 
 
 
 
279
  fig_map = px.scatter_mapbox(
280
  filtered_df,
281
  lat="lat",
282
  lon="lon",
283
  color="RISK_SCORE",
284
  size="total_activity",
285
+ # Traffic Light Colors: Green -> Yellow -> Red
286
+ color_continuous_scale=["#22c55e", "#eab308", "#ef4444"],
287
+ size_max=20,
288
+ zoom=4.5 if selected_state != 'All' else 3.5,
289
+ center={"lat": 22.0, "lon": 80.0}, # Center of India
290
+ hover_name="pincode",
291
+ hover_data={"district": True, "state": True, "RISK_SCORE": True, "lat": False, "lon": False},
292
+ mapbox_style="open-street-map",
293
+ height=600,
294
+ title="<b>Live Fraud Risk Heatmap</b>"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
295
  )
296
+ fig_map.update_layout(margin={"r":0,"t":40,"l":0,"b":0})
297
  st.plotly_chart(fig_map, use_container_width=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
298
  else:
299
+ st.warning("No data matches current filters.")
300
+
301
+ with col_details:
302
+ st.subheader("Top Hotspots")
 
 
303
  if not filtered_df.empty:
304
+ top_districts = filtered_df.groupby('district')['RISK_SCORE'].mean().sort_values(ascending=False).head(5)
305
+ for district, score in top_districts.items():
306
+ # Color code the side bar
307
+ color = "#ef4444" if score > 80 else "#f59e0b"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
308
  st.markdown(f"""
309
+ <div style="background: white; padding: 12px; border-radius: 8px; border-left: 5px solid {color}; margin-bottom: 10px; box-shadow: 0 2px 4px rgba(0,0,0,0.05);">
310
+ <div style="font-weight: 600; color: #1e293b;">{district}</div>
311
+ <div style="font-size: 13px; color: #64748b;">Avg Risk: <b>{score:.1f}</b></div>
 
 
 
 
 
 
 
 
 
 
312
  </div>
313
  """, unsafe_allow_html=True)
314
 
315
+ # --- TAB 2: PRIORITY LIST (DATAFRAME) ---
316
+ with tab_list:
317
+ st.subheader("Target Investigation List")
318
+ st.markdown("Filter: *Showing centers with Risk Score > 75*")
319
+
320
+ target_list = filtered_df[filtered_df['RISK_SCORE'] > 75].sort_values('RISK_SCORE', ascending=False)
321
+
322
+ st.dataframe(
323
+ target_list[['date', 'state', 'district', 'pincode', 'enrol_adult', 'total_activity', 'RISK_SCORE']],
324
+ column_config={
325
+ "RISK_SCORE": st.column_config.ProgressColumn(
326
+ "Risk Probability",
327
+ help="Probability of fraud based on context analysis",
328
+ format="%d%%",
329
+ min_value=0,
330
+ max_value=100,
331
+ ),
332
+ "date": st.column_config.DateColumn("Date", format="DD MMM YYYY"),
333
+ "total_activity": st.column_config.NumberColumn("Volume"),
334
+ "enrol_adult": st.column_config.NumberColumn("Adult Enrols"),
335
+ },
336
+ use_container_width=True,
337
+ hide_index=True,
338
+ height=400
339
+ )
340
 
341
+ # Export Button
342
+ csv = target_list.to_csv(index=False).encode('utf-8')
343
+ st.download_button(
344
+ "📥 Download CSV",
345
+ data=csv,
346
+ file_name="uidai_sentinel_priority_list.csv",
347
+ mime="text/csv",
348
+ type="primary"
349
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
350
 
351
+ # --- TAB 3: CHARTS ---
352
+ with tab_charts:
353
+ c1, c2 = st.columns(2)
354
 
355
+ with c1:
356
+ st.subheader("Ghost ID Pattern (Ratio Deviation)")
357
+ # Scatter Plot
358
  fig_scatter = px.scatter(
359
  filtered_df,
360
  x="total_activity",
361
  y="ratio_deviation",
362
  color="risk_category",
363
+ color_discrete_map={'Critical': '#ef4444', 'High': '#f97316', 'Medium': '#eab308', 'Low': '#22c55e'},
364
+ title="Deviation from District Baseline",
365
+ labels={"ratio_deviation": "Deviation Score", "total_activity": "Daily Transactions"},
366
+ hover_data=['pincode', 'district']
 
 
 
 
 
 
 
 
 
367
  )
368
+ fig_scatter.add_hline(y=0.2, line_dash="dash", line_color="red", annotation_text="Fraud Threshold")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
369
  st.plotly_chart(fig_scatter, use_container_width=True)
370
+
371
+ with c2:
372
+ st.subheader("Risk Distribution")
373
+ # Histogram
374
  fig_hist = px.histogram(
375
+ filtered_df,
376
+ x="RISK_SCORE",
377
+ nbins=20,
378
+ color_discrete_sequence=['#3b82f6'],
379
+ title="Frequency of Risk Scores"
 
 
 
 
 
 
 
 
 
380
  )
381
+ fig_hist.update_layout(bargap=0.1)
382
  st.plotly_chart(fig_hist, use_container_width=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
383
 
384
  # ==========================================
385
+ # 7. FOOTER
386
  # ==========================================
387
  st.markdown("---")
388
  st.markdown("""
389
+ <div style="text-align: center; font-size: 13px; color: #94a3b8;">
390
+ <b>Project Sentinel</b> | UIDAI Hackathon 2026 | Team UIDAI_4571<br>
391
+ <i>Confidential - For Official Use Only</i>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
392
  </div>
393
  """, unsafe_allow_html=True)