LovnishVerma commited on
Commit
f2075fc
Β·
verified Β·
1 Parent(s): 86265dd

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +734 -182
app.py CHANGED
@@ -16,128 +16,300 @@ st.set_page_config(
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 BASE */
27
  .stApp {
28
- background-color: #f8fafc; /* Light Blue-Grey Background */
29
- color: #0f172a; /* Slate 900 Text */
30
- font-family: 'Inter', sans-serif;
31
  }
32
 
33
- /* METRIC CARDS */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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: all 0.2s ease;
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
- /* METRIC TEXT COLORS - Force Dark Text */
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 TEXT FIX (CRITICAL) */
 
 
 
 
 
 
 
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
- .bg-blue { background-color: #dbeafe; color: #1e40af; }
97
 
98
- /* CHART BACKGROUNDS */
99
- .js-plotly-plot .plotly .main-svg {
100
- background-color: rgba(0,0,0,0) !important;
101
  }
102
  </style>
103
  """, unsafe_allow_html=True)
104
 
105
  # ==========================================
106
- # 3. ROBUST DATA LOADING
107
  # ==========================================
108
  @st.cache_data
109
  def load_data():
110
  try:
111
- # Attempt to load user data
112
  df = pd.read_csv('analyzed_aadhaar_data.csv')
113
  except FileNotFoundError:
114
- # FALLBACK: Generate dummy data if file is missing (For Demo Robustness)
115
- dates = pd.date_range(start="2025-01-01", periods=100)
116
  df = pd.DataFrame({
117
  'date': dates,
118
- 'state': np.random.choice(['Maharashtra', 'UP', 'Bihar', 'Karnataka', 'Delhi'], 100),
119
- 'district': np.random.choice(['District A', 'District B', 'District C'], 100),
120
- 'pincode': np.random.randint(110001, 800000, 100),
121
- 'RISK_SCORE': np.random.uniform(20, 99, 100),
122
- 'total_activity': np.random.randint(50, 500, 100),
123
- 'enrol_adult': np.random.randint(10, 200, 100),
124
- 'ratio_deviation': np.random.uniform(-0.1, 0.5, 100),
125
- 'is_weekend': np.random.choice([0, 1], 100, p=[0.7, 0.3])
126
  })
127
 
128
- # Standardize Date
129
  if 'date' in df.columns:
130
  df['date'] = pd.to_datetime(df['date'])
131
 
132
- # ---------------------------------------------------------
133
- # GEOGRAPHIC FIX: Generate Coords Covering ALL India
134
- # ---------------------------------------------------------
135
- np.random.seed(42) # Fixed seed for consistent map
136
- # India Bounds: Lat ~8 to ~32, Lon ~68 to ~97
137
- df['lat'] = np.random.uniform(8.5, 32.0, size=len(df))
138
- df['lon'] = np.random.uniform(70.0, 88.0, size=len(df))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
 
140
- # Risk Categorization
141
  df['risk_category'] = pd.cut(
142
  df['RISK_SCORE'],
143
  bins=[-1, 50, 75, 85, 100],
@@ -146,21 +318,20 @@ def load_data():
146
 
147
  return df
148
 
149
- # Load Data
150
  df = load_data()
151
 
152
  # ==========================================
153
- # 4. SIDEBAR & FILTERS
154
  # ==========================================
155
  with st.sidebar:
156
- st.markdown("### πŸ›‘οΈ Sentinel Control")
157
  st.markdown("---")
158
 
159
- # State Filter
 
160
  state_list = ['All'] + sorted(df['state'].unique().tolist())
161
- selected_state = st.selectbox("πŸ“ Select State", state_list)
162
 
163
- # District Filter (Dynamic)
164
  if selected_state != 'All':
165
  filtered_df = df[df['state'] == selected_state]
166
  district_list = ['All'] + sorted(filtered_df['district'].unique().tolist())
@@ -168,16 +339,16 @@ with st.sidebar:
168
  filtered_df = df.copy()
169
  district_list = ['All']
170
 
171
- selected_district = st.selectbox("πŸ™οΈ Select District", district_list)
172
 
173
  if selected_district != 'All':
174
  filtered_df = filtered_df[filtered_df['district'] == selected_district]
175
-
176
  st.markdown("---")
 
177
 
178
- # Risk Filter
179
  risk_filter = st.multiselect(
180
- "🚨 Risk Level",
181
  options=['Low', 'Medium', 'High', 'Critical'],
182
  default=['High', 'Critical']
183
  )
@@ -186,167 +357,548 @@ with st.sidebar:
186
  filtered_df = filtered_df[filtered_df['risk_category'].isin(risk_filter)]
187
 
188
  st.markdown("---")
189
- st.info(f"**User:** UIDAI_Officer\n\n**Team:** UIDAI_4571")
 
 
 
 
 
 
 
 
 
 
190
 
191
  # ==========================================
192
- # 5. HEADER & KPI SECTION
193
  # ==========================================
194
- col1, col2 = st.columns([3, 1])
195
- with col1:
196
- st.title("Project Sentinel Dashboard")
197
- st.markdown("Context-Aware Fraud Detection System")
 
 
 
 
 
 
 
 
 
 
 
 
198
 
199
- with col2:
200
- # Live Status Indicator
201
- st.markdown("""
202
- <div style="text-align: right; padding-top: 20px;">
203
- <span class="status-badge bg-green">● System Online</span>
204
- <div style="font-size: 12px; color: #64748b; margin-top: 5px;">Last Updated: Just now</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
205
  </div>
206
- """, unsafe_allow_html=True)
 
207
 
208
- st.markdown("---")
209
 
210
- # KPI METRICS
211
- m1, m2, m3, m4 = st.columns(4)
 
212
  total_centers = len(filtered_df)
213
  high_risk = len(filtered_df[filtered_df['RISK_SCORE'] > 75])
 
214
  avg_risk = filtered_df['RISK_SCORE'].mean() if not filtered_df.empty else 0
215
  weekend_alerts = len(filtered_df[(filtered_df['is_weekend'] == 1) & (filtered_df['RISK_SCORE'] > 70)])
216
 
217
- m1.metric("Total Centers Analyzed", f"{total_centers:,}", border=True)
218
- m2.metric("High Risk Alerts", f"{high_risk}", delta="Action Required", delta_color="inverse", border=True)
219
- m3.metric("Avg. Risk Score", f"{avg_risk:.1f}/100", border=True)
220
- m4.metric("Weekend Anomalies", f"{weekend_alerts}", "Unauthorized Activity", delta_color="off", border=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
221
 
222
- st.markdown("##") # Spacer
223
 
224
  # ==========================================
225
- # 6. MAIN TABS
226
  # ==========================================
227
- tab_map, tab_list, tab_charts = st.tabs(["πŸ—ΊοΈ Geographic Risk", "πŸ“‹ Priority List (Action)", "πŸ“Š Pattern Analytics"])
 
 
 
 
 
228
 
229
- # --- TAB 1: ENHANCED MAP ---
230
- with tab_map:
231
- col_map, col_details = st.columns([3, 1])
232
 
233
  with col_map:
234
  if not filtered_df.empty:
235
- # Using Open-Street-Map for better contrast
 
 
 
 
 
 
 
 
 
 
 
 
 
236
  fig_map = px.scatter_mapbox(
237
  filtered_df,
238
  lat="lat",
239
  lon="lon",
240
  color="RISK_SCORE",
241
  size="total_activity",
242
- color_continuous_scale=["#22c55e", "#eab308", "#ef4444"], # Green -> Yellow -> Red
243
- size_max=25,
244
- zoom=4,
245
- center={"lat": 20.5937, "lon": 78.9629}, # Center of India
246
- hover_name="pincode",
247
- hover_data={"district": True, "state": True, "RISK_SCORE": True, "lat": False, "lon": False},
248
- mapbox_style="open-street-map", # Free, High Contrast
249
- height=600,
250
- title="<b>Live Fraud Risk Heatmap</b>"
251
  )
252
- fig_map.update_layout(margin={"r":0,"t":40,"l":0,"b":0})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
  st.plotly_chart(fig_map, use_container_width=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
  else:
255
- st.warning("No data matches current filters.")
256
-
257
- with col_details:
258
- st.subheader("Top Hotspots")
259
- # Aggregated View
 
260
  if not filtered_df.empty:
261
- top_districts = filtered_df.groupby('district')['RISK_SCORE'].mean().sort_values(ascending=False).head(5)
262
- for district, score in top_districts.items():
263
- color = "#ef4444" if score > 80 else "#f59e0b"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
264
  st.markdown(f"""
265
- <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);">
266
- <div style="font-weight: 600; color: #1e293b;">{district}</div>
267
- <div style="font-size: 13px; color: #64748b;">Avg Risk: <b>{score:.1f}</b></div>
 
 
 
 
 
 
 
 
 
 
268
  </div>
269
  """, unsafe_allow_html=True)
270
 
271
- # --- TAB 2: FIXED DATAFRAME ---
272
- with tab_list:
273
- st.subheader("Target Investigation List")
274
- st.markdown("Filter: *Showing centers with Risk Score > 75*")
275
-
276
- # Filter for high risk
277
- target_list = filtered_df[filtered_df['RISK_SCORE'] > 75].sort_values('RISK_SCORE', ascending=False)
278
-
279
- # Display Dataframe with enhanced config
280
- st.dataframe(
281
- target_list[['date', 'state', 'district', 'pincode', 'enrol_adult', 'total_activity', 'RISK_SCORE']],
282
- column_config={
283
- "RISK_SCORE": st.column_config.ProgressColumn(
284
- "Risk Probability",
285
- help="Probability of fraud based on context analysis",
286
- format="%d%%",
287
- min_value=0,
288
- max_value=100,
289
- ),
290
- "date": st.column_config.DateColumn("Date", format="DD MMM YYYY"),
291
- "total_activity": st.column_config.NumberColumn("Volume"),
292
- "enrol_adult": st.column_config.NumberColumn("Adult Enrols"),
293
- },
294
- use_container_width=True,
295
- hide_index=True,
296
- height=400
297
- )
298
 
299
- # Export Buttons
300
- c1, c2 = st.columns([1, 4])
301
- with c1:
302
  st.download_button(
303
- "πŸ“₯ Download CSV",
304
  data=target_list.to_csv(index=False),
305
- file_name="uidai_sentinel_report.csv",
306
  mime="text/csv",
307
- type="primary"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
308
  )
 
 
 
 
309
 
310
- # --- TAB 3: CHARTS ---
311
- with tab_charts:
312
- c1, c2 = st.columns(2)
313
 
314
- with c1:
315
- st.subheader("Ghost ID Pattern (Ratio Deviation)")
316
- # Scatter Plot - Deviation vs Volume
317
  fig_scatter = px.scatter(
318
  filtered_df,
319
  x="total_activity",
320
  y="ratio_deviation",
321
  color="risk_category",
322
- color_discrete_map={'Critical': '#ef4444', 'High': '#f97316', 'Medium': '#eab308', 'Low': '#22c55e'},
323
- title="Deviation from District Baseline",
324
- labels={"ratio_deviation": "Deviation Score", "total_activity": "Daily Transactions"},
325
- hover_data=['pincode', 'district']
 
 
 
 
 
 
 
 
 
326
  )
327
- # Add Threshold Line
328
- fig_scatter.add_hline(y=0.2, line_dash="dash", line_color="red", annotation_text="Fraud Threshold")
 
 
 
 
 
 
 
 
 
 
 
 
 
329
  st.plotly_chart(fig_scatter, use_container_width=True)
330
-
331
- with c2:
332
- st.subheader("Risk Distribution")
 
333
  fig_hist = px.histogram(
334
- filtered_df,
335
- x="RISK_SCORE",
336
- nbins=20,
337
- color_discrete_sequence=['#3b82f6'],
338
- title="Histogram of Risk Scores"
 
 
 
 
 
 
 
 
 
339
  )
340
- fig_hist.update_layout(bargap=0.1)
341
  st.plotly_chart(fig_hist, use_container_width=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
342
 
343
  # ==========================================
344
- # 7. FOOTER
345
  # ==========================================
346
  st.markdown("---")
347
  st.markdown("""
348
- <div style="text-align: center; font-size: 13px; color: #94a3b8;">
349
- <b>Project Sentinel</b> | UIDAI Hackathon 2026 | Team UIDAI_4571<br>
350
- <i>Confidential - For Official Use Only</i>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
351
  </div>
352
  """, unsafe_allow_html=True)
 
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
 
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
  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
  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(
506
+ opacity=0.8,
507
+ line=dict(width=1, color='white')
508
+ )
509
+ )
510
+
511
+ fig_map.update_layout(
512
+ margin={"r":0,"t":10,"l":0,"b":0},
513
+ coloraxis_colorbar=dict(
514
+ title="<b>Risk Score</b>",
515
+ thickness=20,
516
+ len=0.7,
517
+ tickmode='linear',
518
+ tick0=0,
519
+ dtick=20,
520
+ tickfont=dict(size=11),
521
+ titlefont=dict(size=13, family="Inter")
522
+ ),
523
+ font=dict(family="Inter", size=12),
524
+ hoverlabel=dict(
525
+ bgcolor="white",
526
+ font_size=13,
527
+ font_family="Inter"
528
+ )
529
+ )
530
+
531
  st.plotly_chart(fig_map, use_container_width=True)
532
+
533
+ # Add map legend
534
+ st.markdown("""
535
+ <div style="background: white; padding: 15px; border-radius: 8px; margin-top: 10px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
536
+ <div style="display: flex; justify-content: space-around; align-items: center; flex-wrap: wrap; gap: 15px;">
537
+ <div style="display: flex; align-items: center; gap: 8px;">
538
+ <div style="width: 20px; height: 20px; border-radius: 50%; background: #10b981;"></div>
539
+ <span style="font-size: 0.85rem; color: #4a5568;"><b>Low Risk</b> (0-50)</span>
540
+ </div>
541
+ <div style="display: flex; align-items: center; gap: 8px;">
542
+ <div style="width: 20px; height: 20px; border-radius: 50%; background: #fbbf24;"></div>
543
+ <span style="font-size: 0.85rem; color: #4a5568;"><b>Medium Risk</b> (50-75)</span>
544
+ </div>
545
+ <div style="display: flex; align-items: center; gap: 8px;">
546
+ <div style="width: 20px; height: 20px; border-radius: 50%; background: #f97316;"></div>
547
+ <span style="font-size: 0.85rem; color: #4a5568;"><b>High Risk</b> (75-85)</span>
548
+ </div>
549
+ <div style="display: flex; align-items: center; gap: 8px;">
550
+ <div style="width: 20px; height: 20px; border-radius: 50%; background: #ef4444;"></div>
551
+ <span style="font-size: 0.85rem; color: #4a5568;"><b>Critical</b> (85-100)</span>
552
+ </div>
553
+ <div style="display: flex; align-items: center; gap: 8px;">
554
+ <div style="font-size: 0.85rem; color: #718096;">
555
+ <i>Bubble size = Transaction volume</i>
556
+ </div>
557
+ </div>
558
+ </div>
559
+ </div>
560
+ """, unsafe_allow_html=True)
561
  else:
562
+ st.warning("⚠️ No data matches current filters. Please adjust your selection.")
563
+
564
+ with col_sidebar:
565
+ st.markdown("### 🎯 Top Risk Hotspots")
566
+ st.markdown("---")
567
+
568
  if not filtered_df.empty:
569
+ top_districts = filtered_df.groupby('district').agg({
570
+ 'RISK_SCORE': 'mean',
571
+ 'total_activity': 'sum'
572
+ }).sort_values('RISK_SCORE', ascending=False).head(8)
573
+
574
+ for idx, (district, row) in enumerate(top_districts.iterrows(), 1):
575
+ score = row['RISK_SCORE']
576
+ volume = row['total_activity']
577
+
578
+ if score > 85:
579
+ color = "#ef4444"
580
+ label = "CRITICAL"
581
+ elif score > 75:
582
+ color = "#f59e0b"
583
+ label = "HIGH"
584
+ else:
585
+ color = "#10b981"
586
+ label = "MEDIUM"
587
+
588
  st.markdown(f"""
589
+ <div class="hotspot-card" style="border-left-color: {color};">
590
+ <div style="display: flex; justify-content: space-between; align-items: center;">
591
+ <div style="font-weight: 700; color: #1a202c; font-size: 0.95rem;">
592
+ #{idx} {district}
593
+ </div>
594
+ <span style="background: {color}; color: white; padding: 2px 8px; border-radius: 4px; font-size: 0.7rem; font-weight: 700;">
595
+ {label}
596
+ </span>
597
+ </div>
598
+ <div style="display: flex; justify-content: space-between; margin-top: 8px; font-size: 0.85rem;">
599
+ <span style="color: #718096;">Risk: <b style="color: #2d3748;">{score:.1f}</b></span>
600
+ <span style="color: #718096;">Vol: <b style="color: #2d3748;">{int(volume)}</b></span>
601
+ </div>
602
  </div>
603
  """, unsafe_allow_html=True)
604
 
605
+ # --- TAB 2: PRIORITY LIST ---
606
+ with tab2:
607
+ col_header, col_export = st.columns([3, 1])
608
+
609
+ with col_header:
610
+ st.markdown("### 🎯 Investigation Priority Queue")
611
+ st.caption("Sorted by risk score - Immediate action required for scores > 85")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
612
 
613
+ with col_export:
614
+ target_list = filtered_df[filtered_df['RISK_SCORE'] > 75].sort_values('RISK_SCORE', ascending=False)
615
+
616
  st.download_button(
617
+ "πŸ“₯ Export Report",
618
  data=target_list.to_csv(index=False),
619
+ file_name=f"sentinel_report_{datetime.now().strftime('%Y%m%d_%H%M')}.csv",
620
  mime="text/csv",
621
+ type="primary",
622
+ use_container_width=True
623
+ )
624
+
625
+ st.markdown("##")
626
+
627
+ if not target_list.empty:
628
+ st.dataframe(
629
+ target_list[['date', 'state', 'district', 'pincode', 'enrol_adult', 'total_activity', 'ratio_deviation', 'RISK_SCORE']],
630
+ column_config={
631
+ "RISK_SCORE": st.column_config.ProgressColumn(
632
+ "Risk Score",
633
+ help="AI-calculated fraud probability",
634
+ format="%.1f%%",
635
+ min_value=0,
636
+ max_value=100,
637
+ ),
638
+ "date": st.column_config.DateColumn("Date", format="DD MMM YYYY"),
639
+ "total_activity": st.column_config.NumberColumn("Volume", format="%d"),
640
+ "enrol_adult": st.column_config.NumberColumn("Adult Enrollments", format="%d"),
641
+ "ratio_deviation": st.column_config.NumberColumn("Deviation", format="%.3f"),
642
+ "state": "State",
643
+ "district": "District",
644
+ "pincode": st.column_config.NumberColumn("PIN Code", format="%d")
645
+ },
646
+ use_container_width=True,
647
+ hide_index=True,
648
+ height=500
649
  )
650
+
651
+ st.info(f"πŸ“Š **{len(target_list)}** centers flagged for investigation | **{len(target_list[target_list['RISK_SCORE'] > 85])}** critical priority")
652
+ else:
653
+ st.success("βœ… No high-risk centers found with current filters!")
654
 
655
+ # --- TAB 3: ANALYTICS ---
656
+ with tab3:
657
+ col1, col2 = st.columns(2)
658
 
659
+ with col1:
660
+ st.markdown("### πŸ” Ghost ID Pattern Analysis")
661
+
662
  fig_scatter = px.scatter(
663
  filtered_df,
664
  x="total_activity",
665
  y="ratio_deviation",
666
  color="risk_category",
667
+ size="RISK_SCORE",
668
+ color_discrete_map={
669
+ 'Critical': '#dc2626',
670
+ 'High': '#ea580c',
671
+ 'Medium': '#ca8a04',
672
+ 'Low': '#16a34a'
673
+ },
674
+ labels={
675
+ "ratio_deviation": "Deviation from Baseline",
676
+ "total_activity": "Daily Transactions"
677
+ },
678
+ hover_data=['pincode', 'district', 'state'],
679
+ height=400
680
  )
681
+
682
+ fig_scatter.add_hline(
683
+ y=0.2,
684
+ line_dash="dash",
685
+ line_color="red",
686
+ annotation_text="Fraud Threshold (0.2)",
687
+ annotation_position="right"
688
+ )
689
+
690
+ fig_scatter.update_layout(
691
+ font=dict(family="Inter"),
692
+ plot_bgcolor='rgba(0,0,0,0)',
693
+ paper_bgcolor='rgba(0,0,0,0)'
694
+ )
695
+
696
  st.plotly_chart(fig_scatter, use_container_width=True)
697
+
698
+ with col2:
699
+ st.markdown("### πŸ“Š Risk Distribution")
700
+
701
  fig_hist = px.histogram(
702
+ filtered_df,
703
+ x="RISK_SCORE",
704
+ nbins=25,
705
+ color_discrete_sequence=['#8b5cf6'],
706
+ labels={"RISK_SCORE": "Risk Score"},
707
+ height=400
708
+ )
709
+
710
+ fig_hist.update_layout(
711
+ bargap=0.05,
712
+ font=dict(family="Inter"),
713
+ plot_bgcolor='rgba(0,0,0,0)',
714
+ paper_bgcolor='rgba(0,0,0,0)',
715
+ showlegend=False
716
  )
717
+
718
  st.plotly_chart(fig_hist, use_container_width=True)
719
+
720
+ st.markdown("##")
721
+
722
+ col3, col4 = st.columns(2)
723
+
724
+ with col3:
725
+ st.markdown("### πŸ›οΈ State-wise Risk Summary")
726
+
727
+ state_summary = filtered_df.groupby('state').agg({
728
+ 'RISK_SCORE': 'mean',
729
+ 'total_activity': 'sum'
730
+ }).sort_values('RISK_SCORE', ascending=False).reset_index()
731
+
732
+ fig_bar = px.bar(
733
+ state_summary,
734
+ x='state',
735
+ y='RISK_SCORE',
736
+ color='RISK_SCORE',
737
+ color_continuous_scale='RdYlGn_r',
738
+ labels={'RISK_SCORE': 'Avg Risk Score', 'state': 'State'},
739
+ height=400
740
+ )
741
+
742
+ fig_bar.update_layout(
743
+ font=dict(family="Inter"),
744
+ plot_bgcolor='rgba(0,0,0,0)',
745
+ paper_bgcolor='rgba(0,0,0,0)',
746
+ showlegend=False
747
+ )
748
+
749
+ st.plotly_chart(fig_bar, use_container_width=True)
750
+
751
+ with col4:
752
+ st.markdown("### πŸ“ˆ Risk Category Breakdown")
753
+
754
+ risk_counts = filtered_df['risk_category'].value_counts()
755
+
756
+ fig_pie = px.pie(
757
+ values=risk_counts.values,
758
+ names=risk_counts.index,
759
+ color=risk_counts.index,
760
+ color_discrete_map={
761
+ 'Critical': '#dc2626',
762
+ 'High': '#ea580c',
763
+ 'Medium': '#ca8a04',
764
+ 'Low': '#16a34a'
765
+ },
766
+ height=400
767
+ )
768
+
769
+ fig_pie.update_traces(
770
+ textposition='inside',
771
+ textinfo='percent+label',
772
+ hovertemplate='<b>%{label}</b><br>Count: %{value}<br>Percentage: %{percent}<extra></extra>'
773
+ )
774
+
775
+ fig_pie.update_layout(
776
+ font=dict(family="Inter"),
777
+ showlegend=True,
778
+ legend=dict(orientation="h", yanchor="bottom", y=-0.2, xanchor="center", x=0.5)
779
+ )
780
+
781
+ st.plotly_chart(fig_pie, use_container_width=True)
782
+
783
+ # --- TAB 4: TRENDS ---
784
+ with tab4:
785
+ st.markdown("### πŸ“ˆ Temporal Analysis")
786
+
787
+ if 'date' in filtered_df.columns and not filtered_df.empty:
788
+ # Time series of risk scores
789
+ daily_risk = filtered_df.groupby('date').agg({
790
+ 'RISK_SCORE': 'mean',
791
+ 'total_activity': 'sum'
792
+ }).reset_index()
793
+
794
+ fig_trend = go.Figure()
795
+
796
+ fig_trend.add_trace(go.Scatter(
797
+ x=daily_risk['date'],
798
+ y=daily_risk['RISK_SCORE'],
799
+ mode='lines+markers',
800
+ name='Avg Risk Score',
801
+ line=dict(color='#8b5cf6', width=3),
802
+ marker=dict(size=6),
803
+ fill='tozeroy',
804
+ fillcolor='rgba(139, 92, 246, 0.1)'
805
+ ))
806
+
807
+ fig_trend.update_layout(
808
+ height=400,
809
+ font=dict(family="Inter"),
810
+ plot_bgcolor='rgba(0,0,0,0)',
811
+ paper_bgcolor='rgba(0,0,0,0)',
812
+ hovermode='x unified',
813
+ xaxis_title="Date",
814
+ yaxis_title="Average Risk Score"
815
+ )
816
+
817
+ st.plotly_chart(fig_trend, use_container_width=True)
818
+
819
+ st.markdown("##")
820
+
821
+ col1, col2 = st.columns(2)
822
+
823
+ with col1:
824
+ st.markdown("### πŸ“… Weekend vs Weekday Risk")
825
+
826
+ weekend_comparison = filtered_df.groupby('is_weekend').agg({
827
+ 'RISK_SCORE': 'mean',
828
+ 'total_activity': 'sum'
829
+ }).reset_index()
830
+
831
+ weekend_comparison['day_type'] = weekend_comparison['is_weekend'].map({
832
+ 0: 'Weekday',
833
+ 1: 'Weekend'
834
+ })
835
+
836
+ fig_weekend = px.bar(
837
+ weekend_comparison,
838
+ x='day_type',
839
+ y='RISK_SCORE',
840
+ color='day_type',
841
+ color_discrete_map={'Weekday': '#3b82f6', 'Weekend': '#ef4444'},
842
+ labels={'RISK_SCORE': 'Average Risk Score', 'day_type': ''},
843
+ height=400
844
+ )
845
+
846
+ fig_weekend.update_layout(
847
+ font=dict(family="Inter"),
848
+ plot_bgcolor='rgba(0,0,0,0)',
849
+ paper_bgcolor='rgba(0,0,0,0)',
850
+ showlegend=False
851
+ )
852
+
853
+ st.plotly_chart(fig_weekend, use_container_width=True)
854
+
855
+ with col2:
856
+ st.markdown("### πŸ“Š Activity Volume Trends")
857
+
858
+ fig_activity = go.Figure()
859
+
860
+ fig_activity.add_trace(go.Bar(
861
+ x=daily_risk['date'],
862
+ y=daily_risk['total_activity'],
863
+ name='Total Activity',
864
+ marker_color='#06b6d4'
865
+ ))
866
+
867
+ fig_activity.update_layout(
868
+ height=400,
869
+ font=dict(family="Inter"),
870
+ plot_bgcolor='rgba(0,0,0,0)',
871
+ paper_bgcolor='rgba(0,0,0,0)',
872
+ xaxis_title="Date",
873
+ yaxis_title="Total Transactions",
874
+ showlegend=False
875
+ )
876
+
877
+ st.plotly_chart(fig_activity, use_container_width=True)
878
+ else:
879
+ st.warning("⚠️ No temporal data available for analysis.")
880
 
881
  # ==========================================
882
+ # 9. FOOTER
883
  # ==========================================
884
  st.markdown("---")
885
  st.markdown("""
886
+ <div style="background: white; padding: 1.5rem; border-radius: 12px; margin-top: 2rem; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);">
887
+ <div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 1rem;">
888
+ <div style="text-align: left;">
889
+ <div style="font-weight: 700; color: #1a202c; font-size: 1.1rem;">πŸ›‘οΈ Project Sentinel</div>
890
+ <div style="color: #718096; font-size: 0.875rem; margin-top: 4px;">
891
+ AI-Powered Fraud Detection System
892
+ </div>
893
+ </div>
894
+ <div style="text-align: center; color: #4a5568; font-size: 0.85rem;">
895
+ <div><b>UIDAI Hackathon 2026</b></div>
896
+ <div style="margin-top: 4px;">Team UIDAI_4571</div>
897
+ </div>
898
+ <div style="text-align: right; color: #94a3b8; font-size: 0.8rem; font-style: italic;">
899
+ <div>Confidential</div>
900
+ <div>For Official Use Only</div>
901
+ </div>
902
+ </div>
903
  </div>
904
  """, unsafe_allow_html=True)