omgy commited on
Commit
7f90bd0
·
verified ·
1 Parent(s): c95caf7

Update app/routers/admin.py

Browse files
Files changed (1) hide show
  1. app/routers/admin.py +331 -243
app/routers/admin.py CHANGED
@@ -1,243 +1,331 @@
1
- """
2
- Admin router for dashboard metrics and loan management.
3
- Provides endpoints for admin analytics and bulk operations.
4
- """
5
-
6
- from typing import Optional
7
-
8
- from app.schemas import AdminLoansResponse, AdminMetrics, LoanListItem, MessageResponse
9
- from app.services.firebase_service import firebase_service
10
- from app.utils.logger import default_logger as logger
11
- from fastapi import APIRouter, HTTPException, Query, status
12
-
13
- router = APIRouter()
14
-
15
-
16
- @router.get("/metrics", response_model=AdminMetrics)
17
- async def get_admin_metrics():
18
- """
19
- Get aggregated metrics for admin dashboard.
20
-
21
- Returns:
22
- AdminMetrics with loan statistics and analytics
23
- """
24
- try:
25
- logger.info("Fetching admin metrics")
26
-
27
- summary = firebase_service.get_admin_summary()
28
-
29
- metrics = AdminMetrics(
30
- total_applications=summary.get("total_applications", 0),
31
- approved_count=summary.get("approved_count", 0),
32
- rejected_count=summary.get("rejected_count", 0),
33
- adjust_count=summary.get("adjust_count", 0),
34
- avg_loan_amount=round(summary.get("avg_loan_amount", 0), 2),
35
- avg_emi=round(summary.get("avg_emi", 0), 2),
36
- avg_credit_score=round(summary.get("avg_credit_score", 0), 0),
37
- today_applications=summary.get("today_applications", 0),
38
- risk_distribution=summary.get("risk_distribution", {}),
39
- )
40
-
41
- return metrics
42
-
43
- except Exception as e:
44
- logger.error(f"Error fetching admin metrics: {str(e)}")
45
- raise HTTPException(
46
- status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
47
- detail="Failed to fetch admin metrics",
48
- )
49
-
50
-
51
- @router.get("/loans", response_model=AdminLoansResponse)
52
- async def get_all_loans(
53
- page: int = Query(1, ge=1, description="Page number"),
54
- page_size: int = Query(20, ge=1, le=100, description="Items per page"),
55
- decision: Optional[str] = Query(None, description="Filter by decision"),
56
- risk_band: Optional[str] = Query(None, description="Filter by risk band"),
57
- ):
58
- """
59
- Get all loan applications with pagination and filtering.
60
-
61
- Args:
62
- page: Page number (starts at 1)
63
- page_size: Number of items per page
64
- decision: Optional filter by decision (APPROVED/REJECTED/ADJUST)
65
- risk_band: Optional filter by risk band (A/B/C)
66
-
67
- Returns:
68
- AdminLoansResponse with paginated loan list
69
- """
70
- try:
71
- logger.info(f"Fetching loans: page={page}, page_size={page_size}")
72
-
73
- # Calculate offset
74
- offset = (page - 1) * page_size
75
-
76
- # Fetch loans
77
- all_loans = firebase_service.get_all_loans(limit=page_size * 10, offset=0)
78
-
79
- # Apply filters
80
- filtered_loans = all_loans
81
- if decision:
82
- filtered_loans = [
83
- loan for loan in filtered_loans if loan.get("decision") == decision
84
- ]
85
- if risk_band:
86
- filtered_loans = [
87
- loan for loan in filtered_loans if loan.get("risk_band") == risk_band
88
- ]
89
-
90
- # Get total count
91
- total = len(filtered_loans)
92
-
93
- # Apply pagination
94
- start_idx = offset
95
- end_idx = start_idx + page_size
96
- paginated_loans = filtered_loans[start_idx:end_idx]
97
-
98
- # Format loan list
99
- loan_items = []
100
- for loan in paginated_loans:
101
- # Get user profile for full name
102
- user_id = loan.get("user_id")
103
- user_profile = firebase_service.get_user_profile(user_id)
104
- full_name = (
105
- user_profile.get("full_name", "User") if user_profile else "User"
106
- )
107
-
108
- loan_items.append(
109
- LoanListItem(
110
- loan_id=loan.get("loan_id"),
111
- user_id=loan.get("user_id"),
112
- full_name=full_name,
113
- requested_amount=loan.get("requested_amount", 0),
114
- approved_amount=loan.get("approved_amount", 0),
115
- decision=loan.get("decision", "PENDING"),
116
- risk_band=loan.get("risk_band", "C"),
117
- created_at=loan.get("created_at"),
118
- )
119
- )
120
-
121
- response = AdminLoansResponse(
122
- loans=loan_items, total=total, page=page, page_size=page_size
123
- )
124
-
125
- return response
126
-
127
- except Exception as e:
128
- logger.error(f"Error fetching loans: {str(e)}")
129
- raise HTTPException(
130
- status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
131
- detail="Failed to fetch loans",
132
- )
133
-
134
-
135
- @router.get("/stats/summary")
136
- async def get_stats_summary():
137
- """
138
- Get detailed statistics summary.
139
-
140
- Returns:
141
- Detailed statistics including approval rates, average amounts, etc.
142
- """
143
- try:
144
- logger.info("Fetching detailed statistics")
145
-
146
- summary = firebase_service.get_admin_summary()
147
-
148
- total = summary.get("total_applications", 0)
149
- approved = summary.get("approved_count", 0)
150
- rejected = summary.get("rejected_count", 0)
151
- adjust = summary.get("adjust_count", 0)
152
-
153
- # Calculate rates
154
- approval_rate = (approved / total * 100) if total > 0 else 0
155
- rejection_rate = (rejected / total * 100) if total > 0 else 0
156
- adjustment_rate = (adjust / total * 100) if total > 0 else 0
157
-
158
- stats = {
159
- "overview": {
160
- "total_applications": total,
161
- "approved_count": approved,
162
- "rejected_count": rejected,
163
- "adjust_count": adjust,
164
- "today_applications": summary.get("today_applications", 0),
165
- },
166
- "rates": {
167
- "approval_rate": round(approval_rate, 2),
168
- "rejection_rate": round(rejection_rate, 2),
169
- "adjustment_rate": round(adjustment_rate, 2),
170
- },
171
- "averages": {
172
- "avg_loan_amount": round(summary.get("avg_loan_amount", 0), 2),
173
- "avg_emi": round(summary.get("avg_emi", 0), 2),
174
- "avg_credit_score": round(summary.get("avg_credit_score", 0), 0),
175
- },
176
- "risk_distribution": summary.get("risk_distribution", {}),
177
- }
178
-
179
- return stats
180
-
181
- except Exception as e:
182
- logger.error(f"Error fetching stats summary: {str(e)}")
183
- raise HTTPException(
184
- status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
185
- detail="Failed to fetch statistics",
186
- )
187
-
188
-
189
- @router.get("/health")
190
- async def health_check():
191
- """
192
- Health check endpoint for admin services.
193
-
194
- Returns:
195
- Health status
196
- """
197
- try:
198
- # Check Firebase connection
199
- firebase_status = (
200
- "connected" if firebase_service.initialized else "disconnected"
201
- )
202
-
203
- return {
204
- "status": "healthy",
205
- "firebase": firebase_status,
206
- "timestamp": __import__("datetime").datetime.utcnow().isoformat(),
207
- }
208
-
209
- except Exception as e:
210
- logger.error(f"Health check failed: {str(e)}")
211
- raise HTTPException(
212
- status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
213
- detail="Health check failed",
214
- )
215
-
216
-
217
- @router.post("/cleanup")
218
- async def cleanup_old_data():
219
- """
220
- Cleanup old sessions and temporary data.
221
-
222
- Returns:
223
- Cleanup result
224
- """
225
- try:
226
- logger.info("Running cleanup tasks")
227
-
228
- from app.services.session_service import session_service
229
-
230
- # Cleanup old sessions (older than 24 hours)
231
- deleted_sessions = session_service.cleanup_old_sessions(max_age_hours=24)
232
-
233
- return MessageResponse(
234
- message=f"Cleanup completed: {deleted_sessions} sessions removed",
235
- success=True,
236
- )
237
-
238
- except Exception as e:
239
- logger.error(f"Cleanup error: {str(e)}")
240
- raise HTTPException(
241
- status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
242
- detail="Cleanup failed",
243
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Admin router for dashboard metrics and loan management.
3
+ Provides endpoints for admin analytics and bulk operations.
4
+ """
5
+
6
+ from typing import Optional
7
+
8
+ from app.data.mock_profiles import PROFILE_DESCRIPTIONS
9
+ from app.schemas import AdminLoansResponse, AdminMetrics, LoanListItem, MessageResponse
10
+ from app.services.firebase_service import firebase_service
11
+ from app.utils.logger import default_logger as logger
12
+ from fastapi import APIRouter, HTTPException, Query, status
13
+
14
+ router = APIRouter()
15
+
16
+
17
+ @router.get("/metrics", response_model=AdminMetrics)
18
+ async def get_admin_metrics():
19
+ """
20
+ Get aggregated metrics for admin dashboard.
21
+
22
+ Returns:
23
+ AdminMetrics with loan statistics and analytics
24
+ """
25
+ try:
26
+ logger.info("Fetching admin metrics")
27
+
28
+ summary = firebase_service.get_admin_summary()
29
+
30
+ metrics = AdminMetrics(
31
+ total_applications=summary.get("total_applications", 0),
32
+ approved_count=summary.get("approved_count", 0),
33
+ rejected_count=summary.get("rejected_count", 0),
34
+ adjust_count=summary.get("adjust_count", 0),
35
+ avg_loan_amount=round(summary.get("avg_loan_amount", 0), 2),
36
+ avg_emi=round(summary.get("avg_emi", 0), 2),
37
+ avg_credit_score=round(summary.get("avg_credit_score", 0), 0),
38
+ today_applications=summary.get("today_applications", 0),
39
+ risk_distribution=summary.get("risk_distribution", {}),
40
+ )
41
+
42
+ return metrics
43
+
44
+ except Exception as e:
45
+ logger.error(f"Error fetching admin metrics: {str(e)}")
46
+ raise HTTPException(
47
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
48
+ detail="Failed to fetch admin metrics",
49
+ )
50
+
51
+
52
+ @router.get("/loans", response_model=AdminLoansResponse)
53
+ async def get_all_loans(
54
+ page: int = Query(1, ge=1, description="Page number"),
55
+ page_size: int = Query(20, ge=1, le=100, description="Items per page"),
56
+ decision: Optional[str] = Query(None, description="Filter by decision"),
57
+ risk_band: Optional[str] = Query(None, description="Filter by risk band"),
58
+ ):
59
+ """
60
+ Get all loan applications with pagination and filtering.
61
+
62
+ Args:
63
+ page: Page number (starts at 1)
64
+ page_size: Number of items per page
65
+ decision: Optional filter by decision (APPROVED/REJECTED/ADJUST)
66
+ risk_band: Optional filter by risk band (A/B/C)
67
+
68
+ Returns:
69
+ AdminLoansResponse with paginated loan list
70
+ """
71
+ try:
72
+ logger.info(f"Fetching loans: page={page}, page_size={page_size}")
73
+
74
+ # Calculate offset
75
+ offset = (page - 1) * page_size
76
+
77
+ # Fetch loans
78
+ all_loans = firebase_service.get_all_loans(limit=page_size * 10, offset=0)
79
+
80
+ # Apply filters
81
+ filtered_loans = all_loans
82
+ if decision:
83
+ filtered_loans = [
84
+ loan for loan in filtered_loans if loan.get("decision") == decision
85
+ ]
86
+ if risk_band:
87
+ filtered_loans = [
88
+ loan for loan in filtered_loans if loan.get("risk_band") == risk_band
89
+ ]
90
+
91
+ # Get total count
92
+ total = len(filtered_loans)
93
+
94
+ # Apply pagination
95
+ start_idx = offset
96
+ end_idx = start_idx + page_size
97
+ paginated_loans = filtered_loans[start_idx:end_idx]
98
+
99
+ # Format loan list
100
+ loan_items = []
101
+ for loan in paginated_loans:
102
+ # Get user profile for full name
103
+ user_id = loan.get("user_id")
104
+ user_profile = firebase_service.get_user_profile(user_id)
105
+ full_name = (
106
+ user_profile.get("full_name", "User") if user_profile else "User"
107
+ )
108
+
109
+ loan_items.append(
110
+ LoanListItem(
111
+ loan_id=loan.get("loan_id"),
112
+ user_id=loan.get("user_id"),
113
+ full_name=full_name,
114
+ requested_amount=loan.get("requested_amount", 0),
115
+ approved_amount=loan.get("approved_amount", 0),
116
+ decision=loan.get("decision", "PENDING"),
117
+ risk_band=loan.get("risk_band", "C"),
118
+ created_at=loan.get("created_at"),
119
+ )
120
+ )
121
+
122
+ response = AdminLoansResponse(
123
+ loans=loan_items, total=total, page=page, page_size=page_size
124
+ )
125
+
126
+ return response
127
+
128
+ except Exception as e:
129
+ logger.error(f"Error fetching loans: {str(e)}")
130
+ raise HTTPException(
131
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
132
+ detail="Failed to fetch loans",
133
+ )
134
+
135
+
136
+ @router.get("/stats/summary")
137
+ async def get_stats_summary():
138
+ """
139
+ Get detailed statistics summary.
140
+
141
+ Returns:
142
+ Detailed statistics including approval rates, average amounts, etc.
143
+ """
144
+ try:
145
+ logger.info("Fetching detailed statistics")
146
+
147
+ summary = firebase_service.get_admin_summary()
148
+
149
+ total = summary.get("total_applications", 0)
150
+ approved = summary.get("approved_count", 0)
151
+ rejected = summary.get("rejected_count", 0)
152
+ adjust = summary.get("adjust_count", 0)
153
+
154
+ # Calculate rates
155
+ approval_rate = (approved / total * 100) if total > 0 else 0
156
+ rejection_rate = (rejected / total * 100) if total > 0 else 0
157
+ adjustment_rate = (adjust / total * 100) if total > 0 else 0
158
+
159
+ stats = {
160
+ "overview": {
161
+ "total_applications": total,
162
+ "approved_count": approved,
163
+ "rejected_count": rejected,
164
+ "adjust_count": adjust,
165
+ "today_applications": summary.get("today_applications", 0),
166
+ },
167
+ "rates": {
168
+ "approval_rate": round(approval_rate, 2),
169
+ "rejection_rate": round(rejection_rate, 2),
170
+ "adjustment_rate": round(adjustment_rate, 2),
171
+ },
172
+ "averages": {
173
+ "avg_loan_amount": round(summary.get("avg_loan_amount", 0), 2),
174
+ "avg_emi": round(summary.get("avg_emi", 0), 2),
175
+ "avg_credit_score": round(summary.get("avg_credit_score", 0), 0),
176
+ },
177
+ "risk_distribution": summary.get("risk_distribution", {}),
178
+ }
179
+
180
+ return stats
181
+
182
+ except Exception as e:
183
+ logger.error(f"Error fetching admin stats: {str(e)}")
184
+ raise HTTPException(
185
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
186
+ detail="Failed to fetch stats",
187
+ )
188
+
189
+
190
+ @router.get("/user/{user_id}/profile")
191
+ async def get_user_profile_details(user_id: str):
192
+ """
193
+ Get detailed user profile including assigned mock profile info.
194
+
195
+ Args:
196
+ user_id: User ID
197
+
198
+ Returns:
199
+ User profile with mock profile metadata
200
+ """
201
+ try:
202
+ profile = firebase_service.get_user_profile(user_id)
203
+
204
+ if not profile:
205
+ raise HTTPException(
206
+ status_code=status.HTTP_404_NOT_FOUND, detail="User profile not found"
207
+ )
208
+
209
+ # Determine which mock profile category based on credit score
210
+ credit_score = profile.get("mock_credit_score", 0)
211
+ profile_category = "UNKNOWN"
212
+
213
+ if credit_score >= 740:
214
+ profile_category = "YOUNG_PROFESSIONAL"
215
+ elif credit_score >= 670:
216
+ profile_category = "MID_CAREER"
217
+ elif credit_score >= 640:
218
+ profile_category = "ENTRY_LEVEL"
219
+
220
+ # Get profile description
221
+ profile_info = PROFILE_DESCRIPTIONS.get(profile_category, {})
222
+
223
+ return {
224
+ "user_id": user_id,
225
+ "profile_category": profile_category,
226
+ "profile_info": profile_info,
227
+ "financial_data": {
228
+ "monthly_income": profile.get("monthly_income"),
229
+ "existing_emi": profile.get("existing_emi"),
230
+ "credit_score": profile.get("mock_credit_score"),
231
+ "segment": profile.get("segment"),
232
+ "max_eligible_amount": profile.get("max_eligible_amount"),
233
+ "risk_category": profile.get("risk_category"),
234
+ },
235
+ "kyc_data": {
236
+ "kyc_verified": profile.get("kyc_verified"),
237
+ "pan_number": profile.get("pan_number"),
238
+ "bank_name": profile.get("bank_name"),
239
+ "employment_type": profile.get("employment_type"),
240
+ "employment_years": profile.get("employment_years"),
241
+ },
242
+ "full_profile": profile,
243
+ }
244
+
245
+ except HTTPException:
246
+ raise
247
+ except Exception as e:
248
+ logger.error(f"Error fetching user profile: {str(e)}")
249
+ raise HTTPException(
250
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
251
+ detail="Failed to fetch user profile",
252
+ )
253
+
254
+
255
+ @router.get("/profiles/list")
256
+ async def list_mock_profiles():
257
+ """
258
+ List all available mock profile templates.
259
+
260
+ Returns:
261
+ List of mock profile descriptions
262
+ """
263
+ try:
264
+ return {
265
+ "profiles": PROFILE_DESCRIPTIONS,
266
+ "total_profiles": len(PROFILE_DESCRIPTIONS),
267
+ "assignment": "Random profile assigned on signup/login",
268
+ }
269
+ except Exception as e:
270
+ logger.error(f"Error listing profiles: {str(e)}")
271
+ raise HTTPException(
272
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
273
+ detail="Failed to list profiles",
274
+ )
275
+
276
+
277
+ @router.get("/health")
278
+ async def health_check():
279
+ """
280
+ Health check endpoint for admin services.
281
+
282
+ Returns:
283
+ Health status
284
+ """
285
+ try:
286
+ # Check Firebase connection
287
+ firebase_status = (
288
+ "connected" if firebase_service.initialized else "disconnected"
289
+ )
290
+
291
+ return {
292
+ "status": "healthy",
293
+ "firebase": firebase_status,
294
+ "timestamp": __import__("datetime").datetime.utcnow().isoformat(),
295
+ }
296
+
297
+ except Exception as e:
298
+ logger.error(f"Health check failed: {str(e)}")
299
+ raise HTTPException(
300
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
301
+ detail="Health check failed",
302
+ )
303
+
304
+
305
+ @router.post("/cleanup")
306
+ async def cleanup_old_data():
307
+ """
308
+ Cleanup old sessions and temporary data.
309
+
310
+ Returns:
311
+ Cleanup result
312
+ """
313
+ try:
314
+ logger.info("Running cleanup tasks")
315
+
316
+ from app.services.session_service import session_service
317
+
318
+ # Cleanup old sessions (older than 24 hours)
319
+ deleted_sessions = session_service.cleanup_old_sessions(max_age_hours=24)
320
+
321
+ return MessageResponse(
322
+ message=f"Cleanup completed: {deleted_sessions} sessions removed",
323
+ success=True,
324
+ )
325
+
326
+ except Exception as e:
327
+ logger.error(f"Cleanup error: {str(e)}")
328
+ raise HTTPException(
329
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
330
+ detail="Cleanup failed",
331
+ )