LogicGoInfotechSpaces commited on
Commit
a8c5325
·
verified ·
1 Parent(s): b68e9e4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +58 -48
app.py CHANGED
@@ -17,14 +17,15 @@ 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:
29
  raise RuntimeError("Unable to determine default database from MONGO_URI")
30
 
@@ -38,11 +39,16 @@ openai = OpenAI(api_key=OPENAI_API_KEY)
38
  app = FastAPI(title="Financial Health Score Service")
39
 
40
 
 
 
 
41
  class ScoreRequest(BaseModel):
42
  userId: str
43
 
44
 
45
- # ----------------------------- HELPERS ---------------------------------- #
 
 
46
 
47
  def safe_number(value):
48
  try:
@@ -54,8 +60,8 @@ def safe_number(value):
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:
@@ -78,6 +84,7 @@ def normalize_budgets(budgets):
78
  "notifications": budget.get("notifications") or [],
79
  "headCategories": head_categories,
80
  })
 
81
  return normalized
82
 
83
 
@@ -96,7 +103,6 @@ def normalize_transactions(transactions):
96
  "amount": safe_number(txn.get("amount")),
97
  "date": date_str,
98
  })
99
-
100
  return trimmed
101
 
102
 
@@ -119,55 +125,54 @@ Respond ONLY with:
119
  def extract_json_payload(text):
120
  """Extract JSON from plain text or fenced code blocks."""
121
  trimmed = (text or "").strip()
122
- match = re.search(r"```(?:json)?\s*([\s\S]*?)```", trimmed)
123
 
124
- payload = match.group(1).strip() if match else trimmed
125
- return json.loads(payload)
 
 
 
126
 
127
 
 
 
 
 
128
  def extract_openai_text(response):
129
  """
130
- Safely extracts text from ANY OpenAI Chat Completion output.
131
  Handles:
132
- - string output
133
- - list of blocks
134
- - dict with 'content'
135
- - dict with 'text'
136
  """
137
  try:
138
- message = response.choices[0].message
139
  except Exception:
140
  raise HTTPException(status_code=502, detail="OpenAI returned no valid choices")
141
 
142
- # Case: dict with content
143
- if isinstance(message, dict) and "content" in message:
144
- content = message["content"]
145
- else:
146
- content = message
147
-
148
- # Simple string
149
- if isinstance(content, str):
150
- return content.strip()
151
 
152
- # List of blocks
153
- if isinstance(content, list):
154
- output_chunks = []
155
- for block in content:
156
- if isinstance(block, dict) and "text" in block:
157
- output_chunks.append(block["text"])
158
- elif isinstance(block, str):
159
- output_chunks.append(block)
160
- return "\n".join(output_chunks).strip()
161
 
162
- # Dict with text
163
- if isinstance(content, dict) and "text" in content:
164
- return content["text"].strip()
 
 
 
165
 
166
- # Fallback: stringify
167
- return str(content).strip()
168
 
169
 
170
- # ----------------------------- ROUTES ---------------------------------- #
 
 
171
 
172
  @app.get("/")
173
  def root():
@@ -194,7 +199,6 @@ def financial_score(payload: ScoreRequest):
194
  }).sort("date", -1).limit(100)
195
  )
196
 
197
- # If no data
198
  if not budgets and not transactions:
199
  return {
200
  "userId": payload.userId,
@@ -202,10 +206,9 @@ def financial_score(payload: ScoreRequest):
202
  "explanation": "No budgets or transactions found."
203
  }
204
 
205
- # Build LLM prompt
206
  prompt = score_prompt(budgets, transactions)
207
 
208
- # Call OpenAI (new SDK format)
209
  try:
210
  response = openai.chat.completions.create(
211
  model="gpt-4o-mini",
@@ -224,19 +227,26 @@ def financial_score(payload: ScoreRequest):
224
  except Exception:
225
  raise HTTPException(
226
  status_code=502,
227
- detail={"error": "Unable to parse OpenAI response", "rawResponse": model_output}
 
 
 
228
  )
229
 
230
- # Validate format
231
  if "score" not in parsed or "explanation" not in parsed:
232
  raise HTTPException(
233
  status_code=502,
234
- detail={"error": "OpenAI response missing required fields", "rawResponse": model_output}
 
 
 
235
  )
236
 
237
- # Clamp score to 0–100
238
  try:
239
- score_val = max(0, min(100, int(float(parsed["score"]))))
 
240
  except Exception:
241
  score_val = 0
242
 
@@ -244,4 +254,4 @@ def financial_score(payload: ScoreRequest):
244
  "userId": payload.userId,
245
  "score": score_val,
246
  "explanation": parsed["explanation"]
247
- }
 
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
+
29
  if default_db is None:
30
  raise RuntimeError("Unable to determine default database from MONGO_URI")
31
 
 
39
  app = FastAPI(title="Financial Health Score Service")
40
 
41
 
42
+ # ===========================
43
+ # MODELS
44
+ # ===========================
45
  class ScoreRequest(BaseModel):
46
  userId: str
47
 
48
 
49
+ # ===========================
50
+ # HELPERS
51
+ # ===========================
52
 
53
  def safe_number(value):
54
  try:
 
60
  def normalize_budgets(budgets):
61
  normalized = []
62
  for budget in budgets:
 
63
  head_categories = []
64
+ heads = budget.get("headCategories") or []
65
 
66
  if isinstance(heads, list):
67
  for head in heads:
 
84
  "notifications": budget.get("notifications") or [],
85
  "headCategories": head_categories,
86
  })
87
+
88
  return normalized
89
 
90
 
 
103
  "amount": safe_number(txn.get("amount")),
104
  "date": date_str,
105
  })
 
106
  return trimmed
107
 
108
 
 
125
  def extract_json_payload(text):
126
  """Extract JSON from plain text or fenced code blocks."""
127
  trimmed = (text or "").strip()
 
128
 
129
+ fenced = re.search(r"```(?:json)?\s*([\s\S]*?)```", trimmed)
130
+ if fenced:
131
+ return json.loads(fenced.group(1).strip())
132
+
133
+ return json.loads(trimmed)
134
 
135
 
136
+ # ===========================
137
+ # NEW BULLETPROOF OPENAI EXTRACTOR
138
+ # ===========================
139
+
140
  def extract_openai_text(response):
141
  """
142
+ Bulletproof extractor for OpenAI SDK v1.x ChatCompletion responses.
143
  Handles:
144
+ - message.content (real JSON string)
145
+ - ChatCompletionMessage(content='...') object repr (your case)
146
+ - strings
147
+ - lists
148
  """
149
  try:
150
+ msg = response.choices[0].message
151
  except Exception:
152
  raise HTTPException(status_code=502, detail="OpenAI returned no valid choices")
153
 
154
+ # Case 1: content exists normally
155
+ if hasattr(msg, "content"):
156
+ return msg.content.strip()
 
 
 
 
 
 
157
 
158
+ # Case 2: msg is dict containing 'content'
159
+ if isinstance(msg, dict) and "content" in msg:
160
+ return str(msg["content"]).strip()
 
 
 
 
 
 
161
 
162
+ # Case 3: msg is a Python repr:
163
+ # ChatCompletionMessage(content='{"score":...}', role='assistant')
164
+ msg_str = str(msg)
165
+ match = re.search(r"content='([\s\S]*?)'", msg_str)
166
+ if match:
167
+ return match.group(1).strip()
168
 
169
+ # Fallback
170
+ return msg_str.strip()
171
 
172
 
173
+ # ===========================
174
+ # ROUTES
175
+ # ===========================
176
 
177
  @app.get("/")
178
  def root():
 
199
  }).sort("date", -1).limit(100)
200
  )
201
 
 
202
  if not budgets and not transactions:
203
  return {
204
  "userId": payload.userId,
 
206
  "explanation": "No budgets or transactions found."
207
  }
208
 
 
209
  prompt = score_prompt(budgets, transactions)
210
 
211
+ # Call OpenAI
212
  try:
213
  response = openai.chat.completions.create(
214
  model="gpt-4o-mini",
 
227
  except Exception:
228
  raise HTTPException(
229
  status_code=502,
230
+ detail={
231
+ "error": "Unable to parse OpenAI response",
232
+ "rawResponse": model_output
233
+ },
234
  )
235
 
236
+ # Validate required fields
237
  if "score" not in parsed or "explanation" not in parsed:
238
  raise HTTPException(
239
  status_code=502,
240
+ detail={
241
+ "error": "OpenAI response missing required fields",
242
+ "rawResponse": model_output
243
+ }
244
  )
245
 
246
+ # Score limits
247
  try:
248
+ score_val = int(float(parsed["score"]))
249
+ score_val = max(0, min(100, score_val))
250
  except Exception:
251
  score_val = 0
252
 
 
254
  "userId": payload.userId,
255
  "score": score_val,
256
  "explanation": parsed["explanation"]
257
+ }