LogicGoInfotechSpaces commited on
Commit
9f46740
·
verified ·
1 Parent(s): d154aad

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +216 -154
app.py CHANGED
@@ -1,17 +1,6 @@
1
- # main.py
2
- """
3
- Financial Health Score Service (FastAPI)
4
-
5
- FINAL + CREDIT CARD VERSION:
6
- - Budget score
7
- - Transaction score
8
- - Credit card utilization & repayment score
9
- - GPT-4o-mini with JSON schema
10
- - Universal JSON parsing
11
- """
12
-
13
  import json
14
  import os
 
15
  from datetime import datetime, timedelta
16
 
17
  from bson import ObjectId
@@ -20,6 +9,7 @@ from fastapi import FastAPI, HTTPException
20
  from openai import OpenAI
21
  from pydantic import BaseModel
22
  from pymongo import MongoClient
 
23
 
24
  # ===========================
25
  # ENV SETUP
@@ -42,22 +32,51 @@ budget_collection = default_db["budgets"]
42
  transaction_collection = default_db["transactions"]
43
  currencies_collection = default_db["currencies"]
44
  creditcards_collection = default_db["creditcards"]
 
45
 
46
  openai = OpenAI(api_key=OPENAI_API_KEY)
47
 
48
  app = FastAPI(title="Financial Health Score Service")
49
 
50
-
51
  # ===========================
52
  # MODELS
53
  # ===========================
54
  class ScoreRequest(BaseModel):
55
  userId: str
56
 
57
-
58
  # ===========================
59
  # HELPERS
60
  # ===========================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  def safe_number(v):
62
  try:
63
  return float(v)
@@ -102,17 +121,11 @@ def normalize_transactions(txns):
102
  currency_id = txn.get("currency")
103
 
104
  try:
105
- if isinstance(currency_id, ObjectId):
106
- doc = currencies_collection.find_one({"_id": currency_id})
107
- else:
108
- try:
109
- doc = currencies_collection.find_one({"_id": ObjectId(currency_id)})
110
- except:
111
- doc = None
112
  if doc:
113
  currency_code = doc.get("code") or doc.get("currency")
114
  except:
115
- currency_code = None
116
 
117
  out.append({
118
  "type": txn.get("type"),
@@ -129,10 +142,10 @@ def normalize_creditcards(cards):
129
  try:
130
  start_date = c.get("billing_cycle", {}).get("start_date")
131
  end_date = c.get("billing_cycle", {}).get("end_date")
 
 
132
  start_date = start_date.date().isoformat() if isinstance(start_date, datetime) else None
133
  end_date = end_date.date().isoformat() if isinstance(end_date, datetime) else None
134
-
135
- due_date = c.get("due_date")
136
  due_date = due_date.date().isoformat() if isinstance(due_date, datetime) else None
137
  except:
138
  start_date = end_date = due_date = None
@@ -153,25 +166,12 @@ def normalize_creditcards(cards):
153
  def build_prompt(budgets, transactions, creditcards):
154
  return f"""
155
  You are a financial health scoring engine.
156
-
157
  Compute a score (0–100) considering:
158
- 1. Budgets usage
159
- 2. Spending patterns from recent transactions (last 30 days)
160
- 3. Credit card health:
161
- - utilization ratio (balance / credit_limit)
162
- - due amount status
163
- - repayment behavior
164
- - risk from minimum-due payments
165
- - future risk if due_date is near
166
-
167
- Scoring rules:
168
- - A lower credit utilization (<30%) increases score.
169
- - High utilization (>70%) lowers score sharply.
170
- - Overdue or large due amounts reduce score.
171
- - Good budget management increases score.
172
- - Overspending reduces score.
173
- - Make explanation short (2 sentences max).
174
- - MUST follow JSON schema.
175
 
176
  Budgets:
177
  {json.dumps(budgets, indent=2)}
@@ -179,50 +179,87 @@ Budgets:
179
  Transactions:
180
  {json.dumps(transactions, indent=2)}
181
 
182
- CreditCards:
183
  {json.dumps(creditcards, indent=2)}
184
  """
185
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
 
187
  # ===========================
188
- # ROUTES
189
  # ===========================
190
  @app.post("/financial-score")
191
  def financial_score(payload: ScoreRequest):
192
- # Validate ID
 
193
  try:
194
- user_id = ObjectId(payload.userId)
195
  except:
 
 
 
 
 
 
196
  raise HTTPException(status_code=400, detail="Invalid userId")
197
 
198
- # Fetch budgets
199
- budgets_raw = list(budget_collection.find({"createdBy": user_id}))
200
- budgets = normalize_budgets(budgets_raw)
201
-
202
- # Fetch last 30 days transactions
203
- thirty_days_ago = datetime.utcnow() - timedelta(days=30)
204
- txns_raw = list(
205
- transaction_collection.find(
206
- {"user": user_id, "date": {"$gte": thirty_days_ago}}
207
- ).sort("date", -1).limit(200)
208
- )
209
- transactions = normalize_transactions(txns_raw)
210
-
211
- # Fetch credit cards
212
- cards_raw = list(creditcards_collection.find({"user_id": user_id}))
213
- creditcards = normalize_creditcards(cards_raw)
214
-
215
- # No data
216
- if not budgets and not transactions and not creditcards:
217
- return {
218
- "userId": payload.userId,
219
- "score": 0,
220
- "explanation": "No financial data found."
221
- }
222
 
223
- prompt = build_prompt(budgets, transactions, creditcards)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
 
225
- try:
226
  response = openai.chat.completions.create(
227
  model="gpt-4o-mini",
228
  temperature=0.6,
@@ -236,51 +273,52 @@ def financial_score(payload: ScoreRequest):
236
  "score": {"type": "number"},
237
  "explanation": {"type": "string"}
238
  },
239
- "required": ["score", "explanation"],
240
- "additionalProperties": False
241
  }
242
  }
243
  },
244
  messages=[
245
  {"role": "system", "content": "You calculate financial health."},
246
- {"role": "user", "content": prompt}
247
  ]
248
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
249
  except Exception as exc:
250
- raise HTTPException(status_code=502, detail=f"OpenAI request failed: {exc}")
 
 
 
 
 
 
 
 
 
 
251
 
252
- # UNIVERSAL JSON PARSING
253
- try:
254
- content = response.choices[0].message.content
255
 
256
- if isinstance(content, list):
257
- raw_json = content[0].get("text", content[0])
258
- else:
259
- raw_json = content
260
 
261
- if not isinstance(raw_json, str):
262
- raw_json = str(raw_json)
263
 
264
- parsed = json.loads(raw_json)
265
 
266
- except Exception as e:
267
- raise HTTPException(
268
- status_code=502,
269
- detail=f"Could not parse JSON output: {e}"
270
- )
271
 
272
- score_val = parsed.get("score", 0)
273
- try:
274
- score_val = int(float(score_val))
275
- except:
276
- score_val = 0
277
- score_val = max(0, min(100, score_val))
278
 
279
- return {
280
- "userId": payload.userId,
281
- "score": score_val,
282
- "explanation": parsed.get("explanation")
283
- }
284
 
285
 
286
 
@@ -288,11 +326,12 @@ def financial_score(payload: ScoreRequest):
288
  # """
289
  # Financial Health Score Service (FastAPI)
290
 
291
- # FINAL VERSION:
292
- # - Uses GPT-4o-mini with JSON schema
293
- # - Universal-safe JSON parsing (supports all SDK formats)
294
- # - Correct OpenAI Chat Completions API
295
- # - Bulletproof error handling
 
296
  # """
297
 
298
  # import json
@@ -307,7 +346,7 @@ def financial_score(payload: ScoreRequest):
307
  # from pymongo import MongoClient
308
 
309
  # # ===========================
310
- # # ENV & CLIENT SETUP
311
  # # ===========================
312
  # load_dotenv()
313
 
@@ -315,21 +354,19 @@ def financial_score(payload: ScoreRequest):
315
  # OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
316
 
317
  # if not MONGO_URI:
318
- # raise RuntimeError("❌ MONGO_URI missing in environment variables")
319
 
320
  # if not OPENAI_API_KEY:
321
- # raise RuntimeError("❌ OPENAI_API_KEY missing in environment variables")
322
 
323
  # mongo_client = MongoClient(MONGO_URI)
324
  # default_db = mongo_client.get_default_database()
325
- # if default_db is None:
326
- # raise RuntimeError("Unable to determine default database from MONGO_URI")
327
 
328
- # budget_collection = default_db["budget"]
329
  # transaction_collection = default_db["transactions"]
330
  # currencies_collection = default_db["currencies"]
 
331
 
332
- # # OpenAI Client
333
  # openai = OpenAI(api_key=OPENAI_API_KEY)
334
 
335
  # app = FastAPI(title="Financial Health Score Service")
@@ -385,24 +422,17 @@ def financial_score(payload: ScoreRequest):
385
  # date_val = txn.get("date")
386
  # date_str = date_val.date().isoformat() if isinstance(date_val, datetime) else None
387
 
388
- # # Resolve currency
389
  # currency_code = None
390
  # currency_id = txn.get("currency")
391
 
392
  # try:
393
  # if isinstance(currency_id, ObjectId):
394
  # doc = currencies_collection.find_one({"_id": currency_id})
395
- # elif isinstance(currency_id, dict) and "$oid" in currency_id:
396
- # doc = currencies_collection.find_one({"_id": ObjectId(currency_id["$oid"])})
397
- # elif isinstance(currency_id, str):
398
  # try:
399
  # doc = currencies_collection.find_one({"_id": ObjectId(currency_id)})
400
  # except:
401
- # doc = currencies_collection.find_one({"code": currency_id}) or \
402
- # currencies_collection.find_one({"currency": currency_id})
403
- # else:
404
- # doc = None
405
-
406
  # if doc:
407
  # currency_code = doc.get("code") or doc.get("currency")
408
  # except:
@@ -417,36 +447,73 @@ def financial_score(payload: ScoreRequest):
417
  # return out
418
 
419
 
420
- # def build_prompt(budgets, transactions):
421
- # return f"""
422
- # You are a financial wellness expert. Use the user's budgets and last 30 days of transactions
423
- # to compute a financial health score from 0 to 100.
 
 
 
 
424
 
425
- # Rules:
426
- # - ALWAYS prefix currency amounts with currency code (e.g., "INR 10,000").
427
- # - Keep explanation short (1–2 sentences).
428
- # - Score must reflect spending, budget control, and overall balance.
429
- # - Respond ONLY in the JSON schema required below.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
430
 
431
  # Budgets:
432
  # {json.dumps(budgets, indent=2)}
433
 
434
  # Transactions:
435
  # {json.dumps(transactions, indent=2)}
 
 
 
436
  # """
437
 
438
 
439
  # # ===========================
440
  # # ROUTES
441
  # # ===========================
442
- # @app.get("/")
443
- # def root():
444
- # return {"status": "Financial Health Score service running"}
445
-
446
-
447
  # @app.post("/financial-score")
448
  # def financial_score(payload: ScoreRequest):
449
- # # Validate ObjectId
450
  # try:
451
  # user_id = ObjectId(payload.userId)
452
  # except:
@@ -461,23 +528,24 @@ def financial_score(payload: ScoreRequest):
461
  # txns_raw = list(
462
  # transaction_collection.find(
463
  # {"user": user_id, "date": {"$gte": thirty_days_ago}}
464
- # ).sort("date", -1).limit(150)
465
  # )
466
  # transactions = normalize_transactions(txns_raw)
467
 
468
- # # If no data
469
- # if not budgets and not transactions:
 
 
 
 
470
  # return {
471
  # "userId": payload.userId,
472
  # "score": 0,
473
- # "explanation": "No budgets or recent transactions found."
474
  # }
475
 
476
- # prompt = build_prompt(budgets, transactions)
477
 
478
- # # ===========================
479
- # # GPT-4o-mini WITH JSON SCHEMA
480
- # # ===========================
481
  # try:
482
  # response = openai.chat.completions.create(
483
  # model="gpt-4o-mini",
@@ -498,24 +566,20 @@ def financial_score(payload: ScoreRequest):
498
  # }
499
  # },
500
  # messages=[
501
- # {"role": "system", "content": "You are a financial scoring engine."},
502
  # {"role": "user", "content": prompt}
503
  # ]
504
  # )
505
  # except Exception as exc:
506
  # raise HTTPException(status_code=502, detail=f"OpenAI request failed: {exc}")
507
 
508
- # # ===========================
509
- # # UNIVERSAL JSON PARSING (supports ALL SDK formats)
510
- # # ===========================
511
  # try:
512
  # content = response.choices[0].message.content
513
 
514
- # # Case A — list of dicts (new SDK)
515
  # if isinstance(content, list):
516
  # raw_json = content[0].get("text", content[0])
517
  # else:
518
- # # Case B — plain string (older SDK)
519
  # raw_json = content
520
 
521
  # if not isinstance(raw_json, str):
@@ -529,13 +593,11 @@ def financial_score(payload: ScoreRequest):
529
  # detail=f"Could not parse JSON output: {e}"
530
  # )
531
 
532
- # # Score normalization
533
  # score_val = parsed.get("score", 0)
534
  # try:
535
  # score_val = int(float(score_val))
536
  # except:
537
  # score_val = 0
538
-
539
  # score_val = max(0, min(100, score_val))
540
 
541
  # return {
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import json
2
  import os
3
+ import time
4
  from datetime import datetime, timedelta
5
 
6
  from bson import ObjectId
 
9
  from openai import OpenAI
10
  from pydantic import BaseModel
11
  from pymongo import MongoClient
12
+ from pymongo.errors import PyMongoError
13
 
14
  # ===========================
15
  # ENV SETUP
 
32
  transaction_collection = default_db["transactions"]
33
  currencies_collection = default_db["currencies"]
34
  creditcards_collection = default_db["creditcards"]
35
+ api_logs_collection = default_db["api_logs"]
36
 
37
  openai = OpenAI(api_key=OPENAI_API_KEY)
38
 
39
  app = FastAPI(title="Financial Health Score Service")
40
 
 
41
  # ===========================
42
  # MODELS
43
  # ===========================
44
  class ScoreRequest(BaseModel):
45
  userId: str
46
 
 
47
  # ===========================
48
  # HELPERS
49
  # ===========================
50
+ def ist_now():
51
+ return datetime.now().strftime("%d-%m-%Y %H:%M:%S:IST")
52
+
53
+
54
+ def log_api_event(
55
+ *,
56
+ status: str,
57
+ response_time: float,
58
+ user_id: str | None = None,
59
+ error_message: str | None = None
60
+ ):
61
+ payload = {
62
+ "name": "Financial Health Score",
63
+ "status": status,
64
+ "date": ist_now(),
65
+ "response_time": round(response_time, 3),
66
+ }
67
+
68
+ if user_id:
69
+ payload["user_id"] = user_id
70
+
71
+ if error_message:
72
+ payload["error_message"] = error_message
73
+
74
+ try:
75
+ api_logs_collection.insert_one(payload)
76
+ except Exception:
77
+ pass # logging must never break API
78
+
79
+
80
  def safe_number(v):
81
  try:
82
  return float(v)
 
121
  currency_id = txn.get("currency")
122
 
123
  try:
124
+ doc = currencies_collection.find_one({"_id": ObjectId(currency_id)})
 
 
 
 
 
 
125
  if doc:
126
  currency_code = doc.get("code") or doc.get("currency")
127
  except:
128
+ pass
129
 
130
  out.append({
131
  "type": txn.get("type"),
 
142
  try:
143
  start_date = c.get("billing_cycle", {}).get("start_date")
144
  end_date = c.get("billing_cycle", {}).get("end_date")
145
+ due_date = c.get("due_date")
146
+
147
  start_date = start_date.date().isoformat() if isinstance(start_date, datetime) else None
148
  end_date = end_date.date().isoformat() if isinstance(end_date, datetime) else None
 
 
149
  due_date = due_date.date().isoformat() if isinstance(due_date, datetime) else None
150
  except:
151
  start_date = end_date = due_date = None
 
166
  def build_prompt(budgets, transactions, creditcards):
167
  return f"""
168
  You are a financial health scoring engine.
 
169
  Compute a score (0–100) considering:
170
+ - Budget usage
171
+ - Spending behavior
172
+ - Credit card utilization and dues
173
+
174
+ Return JSON only.
 
 
 
 
 
 
 
 
 
 
 
 
175
 
176
  Budgets:
177
  {json.dumps(budgets, indent=2)}
 
179
  Transactions:
180
  {json.dumps(transactions, indent=2)}
181
 
182
+ Credit Cards:
183
  {json.dumps(creditcards, indent=2)}
184
  """
185
 
186
+ # ===========================
187
+ # HEALTH ENDPOINT
188
+ # ===========================
189
+ @app.get("/health")
190
+ def health():
191
+ report = {
192
+ "service": "Financial Health Score Service",
193
+ "status": "healthy",
194
+ "checks": {}
195
+ }
196
+
197
+ try:
198
+ mongo_client.admin.command("ping")
199
+ report["checks"]["mongodb"] = "ok"
200
+ except PyMongoError as e:
201
+ report["checks"]["mongodb"] = f"fail: {e}"
202
+ report["status"] = "degraded"
203
+
204
+ try:
205
+ openai.models.list()
206
+ report["checks"]["openai"] = "ok"
207
+ except Exception as e:
208
+ report["checks"]["openai"] = f"fail: {e}"
209
+ report["status"] = "degraded"
210
+
211
+ report["timestamp"] = datetime.utcnow().isoformat()
212
+ return report
213
 
214
  # ===========================
215
+ # FINANCIAL SCORE
216
  # ===========================
217
  @app.post("/financial-score")
218
  def financial_score(payload: ScoreRequest):
219
+ start_time = time.time()
220
+
221
  try:
222
+ user_oid = ObjectId(payload.userId)
223
  except:
224
+ log_api_event(
225
+ status="fail",
226
+ response_time=time.time() - start_time,
227
+ user_id=payload.userId,
228
+ error_message="Invalid userId"
229
+ )
230
  raise HTTPException(status_code=400, detail="Invalid userId")
231
 
232
+ try:
233
+ budgets = normalize_budgets(
234
+ list(budget_collection.find({"createdBy": user_oid}))
235
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
 
237
+ txns = normalize_transactions(
238
+ list(
239
+ transaction_collection.find(
240
+ {"user": user_oid, "date": {"$gte": datetime.utcnow() - timedelta(days=30)}}
241
+ ).sort("date", -1).limit(200)
242
+ )
243
+ )
244
+
245
+ cards = normalize_creditcards(
246
+ list(creditcards_collection.find({"user_id": user_oid}))
247
+ )
248
+
249
+ if not budgets and not txns and not cards:
250
+ log_api_event(
251
+ status="success",
252
+ response_time=time.time() - start_time,
253
+ user_id=payload.userId
254
+ )
255
+ return {
256
+ "status": "success",
257
+ "message": "No financial data found",
258
+ "userId": payload.userId,
259
+ "score": 0,
260
+ "explanation": "No financial data available."
261
+ }
262
 
 
263
  response = openai.chat.completions.create(
264
  model="gpt-4o-mini",
265
  temperature=0.6,
 
273
  "score": {"type": "number"},
274
  "explanation": {"type": "string"}
275
  },
276
+ "required": ["score", "explanation"]
 
277
  }
278
  }
279
  },
280
  messages=[
281
  {"role": "system", "content": "You calculate financial health."},
282
+ {"role": "user", "content": build_prompt(budgets, txns, cards)}
283
  ]
284
  )
285
+
286
+ parsed = json.loads(response.choices[0].message.content)
287
+ score = max(0, min(100, int(float(parsed.get("score", 0)))))
288
+
289
+ log_api_event(
290
+ status="success",
291
+ response_time=time.time() - start_time,
292
+ user_id=payload.userId
293
+ )
294
+
295
+ return {
296
+ "status": "success",
297
+ "message": "Financial health score calculated successfully",
298
+ "userId": payload.userId,
299
+ "score": score,
300
+ "explanation": parsed.get("explanation")
301
+ }
302
+
303
  except Exception as exc:
304
+ log_api_event(
305
+ status="fail",
306
+ response_time=time.time() - start_time,
307
+ user_id=payload.userId,
308
+ error_message=str(exc)
309
+ )
310
+ raise HTTPException(status_code=502, detail=str(exc))
311
+
312
+
313
+
314
+
315
 
 
 
 
316
 
 
 
 
 
317
 
 
 
318
 
 
319
 
 
 
 
 
 
320
 
 
 
 
 
 
 
321
 
 
 
 
 
 
322
 
323
 
324
 
 
326
  # """
327
  # Financial Health Score Service (FastAPI)
328
 
329
+ # FINAL + CREDIT CARD VERSION:
330
+ # - Budget score
331
+ # - Transaction score
332
+ # - Credit card utilization & repayment score
333
+ # - GPT-4o-mini with JSON schema
334
+ # - Universal JSON parsing
335
  # """
336
 
337
  # import json
 
346
  # from pymongo import MongoClient
347
 
348
  # # ===========================
349
+ # # ENV SETUP
350
  # # ===========================
351
  # load_dotenv()
352
 
 
354
  # OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
355
 
356
  # if not MONGO_URI:
357
+ # raise RuntimeError("❌ MONGO_URI missing")
358
 
359
  # if not OPENAI_API_KEY:
360
+ # raise RuntimeError("❌ OPENAI_API_KEY missing")
361
 
362
  # mongo_client = MongoClient(MONGO_URI)
363
  # default_db = mongo_client.get_default_database()
 
 
364
 
365
+ # budget_collection = default_db["budgets"]
366
  # transaction_collection = default_db["transactions"]
367
  # currencies_collection = default_db["currencies"]
368
+ # creditcards_collection = default_db["creditcards"]
369
 
 
370
  # openai = OpenAI(api_key=OPENAI_API_KEY)
371
 
372
  # app = FastAPI(title="Financial Health Score Service")
 
422
  # date_val = txn.get("date")
423
  # date_str = date_val.date().isoformat() if isinstance(date_val, datetime) else None
424
 
 
425
  # currency_code = None
426
  # currency_id = txn.get("currency")
427
 
428
  # try:
429
  # if isinstance(currency_id, ObjectId):
430
  # doc = currencies_collection.find_one({"_id": currency_id})
431
+ # else:
 
 
432
  # try:
433
  # doc = currencies_collection.find_one({"_id": ObjectId(currency_id)})
434
  # except:
435
+ # doc = None
 
 
 
 
436
  # if doc:
437
  # currency_code = doc.get("code") or doc.get("currency")
438
  # except:
 
447
  # return out
448
 
449
 
450
+ # def normalize_creditcards(cards):
451
+ # out = []
452
+ # for c in cards:
453
+ # try:
454
+ # start_date = c.get("billing_cycle", {}).get("start_date")
455
+ # end_date = c.get("billing_cycle", {}).get("end_date")
456
+ # start_date = start_date.date().isoformat() if isinstance(start_date, datetime) else None
457
+ # end_date = end_date.date().isoformat() if isinstance(end_date, datetime) else None
458
 
459
+ # due_date = c.get("due_date")
460
+ # due_date = due_date.date().isoformat() if isinstance(due_date, datetime) else None
461
+ # except:
462
+ # start_date = end_date = due_date = None
463
+
464
+ # out.append({
465
+ # "card_name": c.get("card_name"),
466
+ # "credit_limit": safe_number(c.get("credit_limit")),
467
+ # "current_balance": safe_number(c.get("current_balance")),
468
+ # "total_due_amount": safe_number(c.get("total_due_amount")),
469
+ # "minimum_due": safe_number(c.get("minimum_due")),
470
+ # "billing_cycle_start": start_date,
471
+ # "billing_cycle_end": end_date,
472
+ # "due_date": due_date,
473
+ # })
474
+ # return out
475
+
476
+
477
+ # def build_prompt(budgets, transactions, creditcards):
478
+ # return f"""
479
+ # You are a financial health scoring engine.
480
+
481
+ # Compute a score (0–100) considering:
482
+ # 1. Budgets usage
483
+ # 2. Spending patterns from recent transactions (last 30 days)
484
+ # 3. Credit card health:
485
+ # - utilization ratio (balance / credit_limit)
486
+ # - due amount status
487
+ # - repayment behavior
488
+ # - risk from minimum-due payments
489
+ # - future risk if due_date is near
490
+
491
+ # Scoring rules:
492
+ # - A lower credit utilization (<30%) increases score.
493
+ # - High utilization (>70%) lowers score sharply.
494
+ # - Overdue or large due amounts reduce score.
495
+ # - Good budget management increases score.
496
+ # - Overspending reduces score.
497
+ # - Make explanation short (2 sentences max).
498
+ # - MUST follow JSON schema.
499
 
500
  # Budgets:
501
  # {json.dumps(budgets, indent=2)}
502
 
503
  # Transactions:
504
  # {json.dumps(transactions, indent=2)}
505
+
506
+ # CreditCards:
507
+ # {json.dumps(creditcards, indent=2)}
508
  # """
509
 
510
 
511
  # # ===========================
512
  # # ROUTES
513
  # # ===========================
 
 
 
 
 
514
  # @app.post("/financial-score")
515
  # def financial_score(payload: ScoreRequest):
516
+ # # Validate ID
517
  # try:
518
  # user_id = ObjectId(payload.userId)
519
  # except:
 
528
  # txns_raw = list(
529
  # transaction_collection.find(
530
  # {"user": user_id, "date": {"$gte": thirty_days_ago}}
531
+ # ).sort("date", -1).limit(200)
532
  # )
533
  # transactions = normalize_transactions(txns_raw)
534
 
535
+ # # Fetch credit cards
536
+ # cards_raw = list(creditcards_collection.find({"user_id": user_id}))
537
+ # creditcards = normalize_creditcards(cards_raw)
538
+
539
+ # # No data
540
+ # if not budgets and not transactions and not creditcards:
541
  # return {
542
  # "userId": payload.userId,
543
  # "score": 0,
544
+ # "explanation": "No financial data found."
545
  # }
546
 
547
+ # prompt = build_prompt(budgets, transactions, creditcards)
548
 
 
 
 
549
  # try:
550
  # response = openai.chat.completions.create(
551
  # model="gpt-4o-mini",
 
566
  # }
567
  # },
568
  # messages=[
569
+ # {"role": "system", "content": "You calculate financial health."},
570
  # {"role": "user", "content": prompt}
571
  # ]
572
  # )
573
  # except Exception as exc:
574
  # raise HTTPException(status_code=502, detail=f"OpenAI request failed: {exc}")
575
 
576
+ # # UNIVERSAL JSON PARSING
 
 
577
  # try:
578
  # content = response.choices[0].message.content
579
 
 
580
  # if isinstance(content, list):
581
  # raw_json = content[0].get("text", content[0])
582
  # else:
 
583
  # raw_json = content
584
 
585
  # if not isinstance(raw_json, str):
 
593
  # detail=f"Could not parse JSON output: {e}"
594
  # )
595
 
 
596
  # score_val = parsed.get("score", 0)
597
  # try:
598
  # score_val = int(float(score_val))
599
  # except:
600
  # score_val = 0
 
601
  # score_val = max(0, min(100, score_val))
602
 
603
  # return {