LogicGoInfotechSpaces commited on
Commit
bcb8d98
·
verified ·
1 Parent(s): 5638124

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +73 -23
app.py CHANGED
@@ -10,17 +10,19 @@ from openai import OpenAI
10
  from pydantic import BaseModel
11
  from pymongo import MongoClient
12
 
 
13
  load_dotenv()
14
 
15
  MONGO_URI = os.getenv("MONGO_URI")
16
  OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
17
 
18
  if not MONGO_URI:
19
- raise RuntimeError("MONGO_URI missing in environment variables")
20
 
21
  if not OPENAI_API_KEY:
22
- raise RuntimeError("OPENAI_API_KEY missing in environment variables")
23
 
 
24
  mongo_client = MongoClient(MONGO_URI)
25
  default_db = mongo_client.get_default_database()
26
  if default_db is None:
@@ -29,17 +31,20 @@ if default_db is None:
29
  budget_collection = default_db["budget"]
30
  transaction_collection = default_db["transactions"]
31
 
 
32
  openai = OpenAI(api_key=OPENAI_API_KEY)
 
 
33
  app = FastAPI(title="Financial Health Score Service")
34
 
35
 
 
36
  class ScoreRequest(BaseModel):
37
  userId: str
38
 
39
 
 
40
  def safe_number(value):
41
- if isinstance(value, (int, float)):
42
- return value
43
  try:
44
  return float(value)
45
  except Exception:
@@ -49,8 +54,9 @@ def safe_number(value):
49
  def normalize_budgets(budgets):
50
  normalized = []
51
  for budget in budgets:
52
- head_categories = []
53
  heads = budget.get("headCategories") or []
 
 
54
  if isinstance(heads, list):
55
  for head in heads:
56
  head_categories.append({
@@ -72,16 +78,17 @@ def normalize_budgets(budgets):
72
  "notifications": budget.get("notifications") or [],
73
  "headCategories": head_categories,
74
  })
 
75
  return normalized
76
 
77
 
78
  def normalize_transactions(transactions):
79
  trimmed = []
80
  for txn in transactions:
81
- date_value = txn.get("date")
82
  date_str = None
83
- if isinstance(date_value, datetime):
84
- date_str = date_value.date().isoformat()
 
85
  trimmed.append({
86
  "type": txn.get("type"),
87
  "amount": safe_number(txn.get("amount")),
@@ -92,7 +99,8 @@ def normalize_transactions(transactions):
92
 
93
  def score_prompt(budgets, transactions):
94
  return f"""
95
- You are a financial wellness expert. Using any available budgets and recent transactions below, rate the user's financial health on a scale of 0-100.
 
96
 
97
  Budgets:
98
  {json.dumps(normalize_budgets(budgets), indent=2)}
@@ -100,11 +108,15 @@ Budgets:
100
  Transactions (last 30 days):
101
  {json.dumps(normalize_transactions(transactions), indent=2)}
102
 
103
- Respond ONLY with: {{"score": number, "explanation": "text"}}
 
104
  """
105
 
106
 
107
  def extract_json_payload(text):
 
 
 
108
  trimmed = (text or "").strip()
109
  match = re.search(r"```(?:json)?\s*([\s\S]*?)```", trimmed)
110
  payload = match.group(1).strip() if match else trimmed
@@ -118,37 +130,75 @@ def root():
118
 
119
  @app.post("/financial-score")
120
  def financial_score(payload: ScoreRequest):
 
121
  try:
122
  user_id = ObjectId(payload.userId)
123
  except Exception:
124
  raise HTTPException(status_code=400, detail="Invalid userId")
125
 
 
126
  budgets = list(budget_collection.find({"createdBy": user_id}))
 
 
127
  thirty_days_ago = datetime.utcnow() - timedelta(days=30)
128
  transactions = list(
129
  transaction_collection.find({
130
  "user": user_id,
131
- "date": {"$gte": thirty_days_ago},
132
  }).sort("date", -1).limit(100)
133
  )
134
 
 
135
  if not budgets and not transactions:
136
  return {
137
  "userId": payload.userId,
138
  "score": 0,
139
- "explanation": "No budgets or transactions found.",
140
  }
141
 
142
- response = openai.responses.create(
143
- model="gpt-4o-mini",
144
- input=score_prompt(budgets, transactions),
145
- temperature=0.6,
146
- )
147
-
148
- model_output = response.output[0].content[0].text
149
- parsed = extract_json_payload(model_output)
150
 
151
- score = max(0, min(100, int(parsed["score"])))
152
- explanation = parsed["explanation"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
 
154
- return {"userId": payload.userId, "score": score, "explanation": explanation}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  from pydantic import BaseModel
11
  from pymongo import MongoClient
12
 
13
+ # Load .env variables
14
  load_dotenv()
15
 
16
  MONGO_URI = os.getenv("MONGO_URI")
17
  OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
18
 
19
  if not MONGO_URI:
20
+ raise RuntimeError("MONGO_URI missing in environment variables")
21
 
22
  if not OPENAI_API_KEY:
23
+ raise RuntimeError("OPENAI_API_KEY missing in environment variables")
24
 
25
+ # MongoDB Setup
26
  mongo_client = MongoClient(MONGO_URI)
27
  default_db = mongo_client.get_default_database()
28
  if default_db is None:
 
31
  budget_collection = default_db["budget"]
32
  transaction_collection = default_db["transactions"]
33
 
34
+ # OpenAI Client
35
  openai = OpenAI(api_key=OPENAI_API_KEY)
36
+
37
+ # FastAPI App
38
  app = FastAPI(title="Financial Health Score Service")
39
 
40
 
41
+ # Request Model
42
  class ScoreRequest(BaseModel):
43
  userId: str
44
 
45
 
46
+ # Helpers
47
  def safe_number(value):
 
 
48
  try:
49
  return float(value)
50
  except Exception:
 
54
  def normalize_budgets(budgets):
55
  normalized = []
56
  for budget in budgets:
 
57
  heads = budget.get("headCategories") or []
58
+ head_categories = []
59
+
60
  if isinstance(heads, list):
61
  for head in heads:
62
  head_categories.append({
 
78
  "notifications": budget.get("notifications") or [],
79
  "headCategories": head_categories,
80
  })
81
+
82
  return normalized
83
 
84
 
85
  def normalize_transactions(transactions):
86
  trimmed = []
87
  for txn in transactions:
 
88
  date_str = None
89
+ if isinstance(txn.get("date"), datetime):
90
+ date_str = txn["date"].date().isoformat()
91
+
92
  trimmed.append({
93
  "type": txn.get("type"),
94
  "amount": safe_number(txn.get("amount")),
 
99
 
100
  def score_prompt(budgets, transactions):
101
  return f"""
102
+ You are a financial wellness expert. Using any available budgets and recent transactions below,
103
+ rate the user's financial health on a scale of 0–100.
104
 
105
  Budgets:
106
  {json.dumps(normalize_budgets(budgets), indent=2)}
 
108
  Transactions (last 30 days):
109
  {json.dumps(normalize_transactions(transactions), indent=2)}
110
 
111
+ Respond ONLY with:
112
+ {{"score": number, "explanation": "short explanation"}}
113
  """
114
 
115
 
116
  def extract_json_payload(text):
117
+ """
118
+ Removes markdown fences and extracts pure JSON.
119
+ """
120
  trimmed = (text or "").strip()
121
  match = re.search(r"```(?:json)?\s*([\s\S]*?)```", trimmed)
122
  payload = match.group(1).strip() if match else trimmed
 
130
 
131
  @app.post("/financial-score")
132
  def financial_score(payload: ScoreRequest):
133
+ # Validate userId
134
  try:
135
  user_id = ObjectId(payload.userId)
136
  except Exception:
137
  raise HTTPException(status_code=400, detail="Invalid userId")
138
 
139
+ # Fetch budgets
140
  budgets = list(budget_collection.find({"createdBy": user_id}))
141
+
142
+ # Fetch transactions (last 30 days)
143
  thirty_days_ago = datetime.utcnow() - timedelta(days=30)
144
  transactions = list(
145
  transaction_collection.find({
146
  "user": user_id,
147
+ "date": {"$gte": thirty_days_ago}
148
  }).sort("date", -1).limit(100)
149
  )
150
 
151
+ # No data fallback
152
  if not budgets and not transactions:
153
  return {
154
  "userId": payload.userId,
155
  "score": 0,
156
+ "explanation": "No budgets or transactions found."
157
  }
158
 
159
+ # Build LLM Prompt
160
+ prompt = score_prompt(budgets, transactions)
 
 
 
 
 
 
161
 
162
+ # Call OpenAI (new SDK)
163
+ try:
164
+ response = openai.chat.completions.create(
165
+ model="gpt-4o-mini",
166
+ temperature=0.6,
167
+ messages=[{"role": "user", "content": prompt}],
168
+ )
169
+ except Exception as exc:
170
+ raise HTTPException(status_code=502, detail=f"OpenAI request failed: {exc}")
171
+
172
+ # Extract content text
173
+ try:
174
+ model_output = response.choices[0].message["content"]
175
+ except Exception:
176
+ raise HTTPException(
177
+ status_code=502,
178
+ detail="Invalid response format from OpenAI"
179
+ )
180
 
181
+ # Parse JSON response
182
+ try:
183
+ parsed = extract_json_payload(model_output)
184
+ except Exception:
185
+ raise HTTPException(
186
+ status_code=502,
187
+ detail={"error": "Unable to parse OpenAI response", "rawResponse": model_output}
188
+ )
189
+
190
+ # Validate fields
191
+ if "score" not in parsed or "explanation" not in parsed:
192
+ raise HTTPException(
193
+ status_code=502,
194
+ detail={"error": "Invalid OpenAI response", "rawResponse": model_output}
195
+ )
196
+
197
+ # Clamp score 0–100
198
+ score_val = max(0, min(100, int(parsed["score"])))
199
+
200
+ return {
201
+ "userId": payload.userId,
202
+ "score": score_val,
203
+ "explanation": parsed["explanation"]
204
+ }