rairo commited on
Commit
dd35a8c
·
verified ·
1 Parent(s): cd3af11

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +222 -108
main.py CHANGED
@@ -17,23 +17,31 @@ import google.generativeai as genai
17
  import uuid
18
  import base64
19
  from io import BytesIO
20
- import requests # Added for making API calls
 
21
 
22
  load_dotenv()
23
 
24
  app = Flask(__name__)
25
  cors = CORS(app)
26
 
27
-
 
 
 
 
 
28
 
29
  class FlaskResponse(ResponseParser):
30
  def __init__(self, context):
31
  super().__init__(context)
32
 
33
  def format_dataframe(self, result):
 
34
  return result["value"].to_html()
35
 
36
  def format_plot(self, result):
 
37
  val = result["value"]
38
  # If val is a matplotlib figure, handle it accordingly.
39
  if hasattr(val, "savefig"):
@@ -42,32 +50,42 @@ class FlaskResponse(ResponseParser):
42
  val.savefig(buf, format="png")
43
  buf.seek(0)
44
  image_base64 = base64.b64encode(buf.read()).decode("utf-8")
 
45
  return f"data:image/png;base64,{image_base64}"
46
  except Exception as e:
47
- print("Error processing figure:", e)
48
  return str(val)
49
  # If val is a string and is a valid file path, read and encode it.
50
  if isinstance(val, str) and os.path.isfile(os.path.join(val)):
51
  image_path = os.path.join(val)
52
- print("My image path:", image_path)
53
  with open(image_path, "rb") as file:
54
  data = file.read()
55
  base64_data = base64.b64encode(data).decode("utf-8")
 
56
  return f"data:image/png;base64,{base64_data}"
57
  # Fallback: return as a string.
 
58
  return str(val)
59
 
60
  def format_other(self, result):
 
61
  # For non-image responses, simply return the value as a string.
62
  return str(result["value"])
63
 
64
 
 
 
65
  gemini_api_key = os.getenv('Gemini')
66
- # --- Model name reverted to your original specification ---
67
- llm = ChatGoogleGenerativeAI(api_key=gemini_api_key, model='gemini-2.0-flash-thinking-exp', temperature=0.1)
 
68
 
69
- gemini_api_key = os.environ['Gemini']
 
 
70
 
 
71
  genai.configure(api_key=gemini_api_key)
72
 
73
  generation_config = {
@@ -82,99 +100,163 @@ model = genai.GenerativeModel(
82
  generation_config=generation_config,
83
  )
84
 
85
-
86
  guid = uuid.uuid4()
87
  new_filename = f"{guid}"
88
  user_defined_path = os.path.join("/exports/charts", new_filename)
89
-
90
 
91
  # --- REFACTORED Endpoint for chat ---
92
  @app.route("/chat", methods=["POST"])
93
  @cross_origin()
94
  def bot():
95
- # Retrieve parameters from the request
96
- profile_id = request.json.get("profile_id")
97
- user_question = request.json.get("user_question")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
 
99
- if not profile_id or not user_question:
100
- return jsonify({"error": "Missing 'profile_id' or 'user_question' in request."}), 400
 
101
 
102
- print(f"Received request for profile_id: {profile_id}")
103
- print(f"User question: {user_question}")
104
 
105
- # Fetch data from the external API
106
- API_URL = "https://irisplustech.com/public/api4/business/profile/user/get-recent-transactions-v2"
107
- payload = {'profile_id': profile_id}
108
 
109
- try:
110
- response = requests.post(API_URL, data=payload)
111
- # Check if the request was successful
112
- if response.status_code != 200:
113
- return jsonify({
114
- "error": "Failed to fetch data from the transaction API.",
115
- "status_code": response.status_code,
116
- "details": response.text
117
- }), 502 # Bad Gateway
118
-
119
- api_data = response.json()
120
-
121
- # Check for API-level errors
122
- if api_data.get("error"):
123
- return jsonify({
124
- "error": "Transaction API returned an error.",
125
- "message": api_data.get("message", "No message provided.")
126
- }), 400
127
-
128
- transactions = api_data.get("transactions")
129
- if transactions is None or not isinstance(transactions, list):
130
- return jsonify({"error": "Invalid data format from transaction API. 'transactions' key is missing or not a list."}), 500
131
 
132
- if not transactions:
133
- return jsonify({"answer": "No transaction data was found for this profile. I can't answer any questions."})
134
-
135
- # Convert the transaction data into a dataframe
136
- df = pd.DataFrame(transactions)
137
- print("Columns in dataframe:", list(df.columns))
138
-
139
- # Create a SmartDataframe instance using your configuration.
140
- pandas_agent = SmartDataframe(
141
- df,
142
- config={
143
- "llm": llm,
144
- "response_parser": FlaskResponse,
145
- "custom_whitelisted_dependencies": [
146
- "os", "io", "sys", "chr", "glob",
147
- "b64decoder", "collections", "geopy",
148
- "geopandas", "wordcloud", "builtins"
149
- ],
150
- "security": "none", "save_charts_path": user_defined_path,
151
- "save_charts": False, "enable_cache": False, "conversational":True
152
- }
153
- )
154
-
155
- # Get the answer from the agent
156
- answer = pandas_agent.chat(user_question)
157
-
158
- # Process the answer based on its type
159
- formatted_answer = None
160
- if isinstance(answer, pd.DataFrame):
161
- formatted_answer = answer.to_html()
162
- elif isinstance(answer, plt.Figure):
163
- buf = io.BytesIO()
164
- answer.savefig(buf, format="png")
165
- buf.seek(0)
166
- image_base64 = base64.b64encode(buf.read()).decode("utf-8")
167
- formatted_answer = f"data:image/png;base64,{image_base64}"
168
- else:
169
- formatted_answer = str(answer)
170
-
171
- # Return the formatted answer as JSON.
172
- return jsonify({"answer": formatted_answer})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
 
174
- except requests.exceptions.RequestException as e:
175
- return jsonify({"error": "Could not connect to the transaction API.", "details": str(e)}), 500
176
  except Exception as e:
177
- logging.exception("An unexpected error occurred in /chat endpoint")
178
  return jsonify({"error": "An unexpected server error occurred.", "details": str(e)}), 500
179
 
180
 
@@ -182,56 +264,88 @@ def bot():
182
  @app.route("/report", methods=["POST"])
183
  @cross_origin()
184
  def busines_report():
185
- json_data = request.json.get("json_data")
 
 
 
 
 
 
186
 
187
- prompt = """
188
  You are Quantilytix business analyst. Analyze the following data and generate a comprehensive and insightful business report, including appropriate key perfomance indicators and recommendations Use markdown formatting and tables where necessary. only return the report and nothing else.
189
  data:
190
  """ + str(json_data)
191
 
192
-
193
- response = model.generate_content(prompt)
194
- report = response.text
 
195
 
 
 
 
 
 
196
 
197
- return jsonify(str(report))
198
 
199
- # MArketing
200
  @app.route("/marketing", methods=["POST"])
201
  @cross_origin()
202
  def marketing():
203
- json_data = request.json.get("json_data")
 
 
 
 
 
 
204
 
205
- prompt = """
206
  You are an Quantilytix Marketing Specialist. Analyze the following data and generate a comprehensive marketing strategy, Only return the marketing strategy. be very creative:
207
  """ + str(json_data)
208
 
209
-
210
- response = model.generate_content(prompt)
211
- report = response.text
 
212
 
 
 
 
 
 
213
 
214
- return jsonify(str(report))
215
 
216
- # Business Plan endpoint REMOVED
217
-
218
- #Notificatiions
219
  @app.route("/notify", methods=["POST"])
220
  @cross_origin()
221
  def notifications():
222
- json_data = request.json.get("json_data")
 
 
 
 
 
 
223
 
224
- prompt = """
225
  You are Quantilytix business analyst. Write a very brief analysis and marketing tips using this business data. your output should be suitable for a notification dashboard so no quips.
226
  """ + str(json_data)
227
 
228
-
229
- response = model.generate_content(prompt)
230
- report = response.text
231
-
232
 
233
- return jsonify(str(report))
 
 
 
 
234
 
235
 
236
  if __name__ == "__main__":
 
 
237
  app.run(debug=True, host="0.0.0.0", port=7860)
 
17
  import uuid
18
  import base64
19
  from io import BytesIO
20
+ import requests
21
+ import urllib.parse # Added for URL encoding
22
 
23
  load_dotenv()
24
 
25
  app = Flask(__name__)
26
  cors = CORS(app)
27
 
28
+ # Set up logging configuration
29
+ logging.basicConfig(
30
+ level=logging.DEBUG,
31
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
32
+ )
33
+ logger = logging.getLogger(__name__)
34
 
35
  class FlaskResponse(ResponseParser):
36
  def __init__(self, context):
37
  super().__init__(context)
38
 
39
  def format_dataframe(self, result):
40
+ logger.debug("Formatting dataframe result")
41
  return result["value"].to_html()
42
 
43
  def format_plot(self, result):
44
+ logger.debug("Formatting plot result")
45
  val = result["value"]
46
  # If val is a matplotlib figure, handle it accordingly.
47
  if hasattr(val, "savefig"):
 
50
  val.savefig(buf, format="png")
51
  buf.seek(0)
52
  image_base64 = base64.b64encode(buf.read()).decode("utf-8")
53
+ logger.debug("Successfully converted matplotlib figure to base64")
54
  return f"data:image/png;base64,{image_base64}"
55
  except Exception as e:
56
+ logger.error(f"Error processing figure: {e}")
57
  return str(val)
58
  # If val is a string and is a valid file path, read and encode it.
59
  if isinstance(val, str) and os.path.isfile(os.path.join(val)):
60
  image_path = os.path.join(val)
61
+ logger.debug(f"Processing image path: {image_path}")
62
  with open(image_path, "rb") as file:
63
  data = file.read()
64
  base64_data = base64.b64encode(data).decode("utf-8")
65
+ logger.debug("Successfully converted image file to base64")
66
  return f"data:image/png;base64,{base64_data}"
67
  # Fallback: return as a string.
68
+ logger.debug("Returning plot result as string")
69
  return str(val)
70
 
71
  def format_other(self, result):
72
+ logger.debug("Formatting other result type")
73
  # For non-image responses, simply return the value as a string.
74
  return str(result["value"])
75
 
76
 
77
+ logger.info("Initializing models...")
78
+
79
  gemini_api_key = os.getenv('Gemini')
80
+ if not gemini_api_key:
81
+ logger.error("Gemini API key not found in environment variables")
82
+ raise ValueError("Gemini API key is required")
83
 
84
+ logger.info("Setting up ChatGoogleGenerativeAI...")
85
+ # --- Model name reverted to your original specification ---
86
+ llm = ChatGoogleGenerativeAI(api_key=gemini_api_key, model='gemini-2.0-flash', temperature=0.1)
87
 
88
+ logger.info("Configuring genai...")
89
  genai.configure(api_key=gemini_api_key)
90
 
91
  generation_config = {
 
100
  generation_config=generation_config,
101
  )
102
 
 
103
  guid = uuid.uuid4()
104
  new_filename = f"{guid}"
105
  user_defined_path = os.path.join("/exports/charts", new_filename)
106
+ logger.info(f"Chart export path set to: {user_defined_path}")
107
 
108
  # --- REFACTORED Endpoint for chat ---
109
  @app.route("/chat", methods=["POST"])
110
  @cross_origin()
111
  def bot():
112
+ logger.info("=== Starting /chat endpoint ===")
113
+
114
+ try:
115
+ # Log the incoming request
116
+ logger.debug(f"Request headers: {dict(request.headers)}")
117
+ logger.debug(f"Request data: {request.get_data()}")
118
+
119
+ # Retrieve parameters from the request
120
+ request_json = request.get_json()
121
+ logger.debug(f"Parsed JSON: {request_json}")
122
+
123
+ if not request_json:
124
+ logger.error("No JSON data in request")
125
+ return jsonify({"error": "No JSON data provided in request."}), 400
126
+
127
+ profile_id = request_json.get("profile_id")
128
+ user_question = request_json.get("user_question")
129
+
130
+ logger.info(f"Extracted profile_id: {profile_id}")
131
+ logger.info(f"Extracted user_question: {user_question}")
132
 
133
+ if not profile_id or not user_question:
134
+ logger.error("Missing required parameters")
135
+ return jsonify({"error": "Missing 'profile_id' or 'user_question' in request."}), 400
136
 
137
+ logger.info(f"Processing request for profile_id: {profile_id}")
138
+ logger.info(f"User question: {user_question}")
139
 
140
+ # URL encode the profile_id
141
+ encoded_profile_id = urllib.parse.quote_plus(str(profile_id))
142
+ logger.info(f"URL encoded profile_id: {encoded_profile_id}")
143
 
144
+ # Fetch data from the external API
145
+ API_URL = "https://irisplustech.com/public/api4/business/profile/user/get-recent-transactions-v2"
146
+ payload = {'profile_id': encoded_profile_id}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
 
148
+ logger.info(f"Making API call to: {API_URL}")
149
+ logger.debug(f"API payload: {payload}")
150
+
151
+ try:
152
+ logger.info("Sending POST request to transaction API...")
153
+ response = requests.post(API_URL, data=payload, timeout=30)
154
+ logger.info(f"API response status code: {response.status_code}")
155
+ logger.debug(f"API response headers: {dict(response.headers)}")
156
+
157
+ # Check if the request was successful
158
+ if response.status_code != 200:
159
+ logger.error(f"API request failed with status {response.status_code}")
160
+ logger.error(f"API response text: {response.text}")
161
+ return jsonify({
162
+ "error": "Failed to fetch data from the transaction API.",
163
+ "status_code": response.status_code,
164
+ "details": response.text
165
+ }), 502 # Bad Gateway
166
+
167
+ logger.info("API request successful, parsing JSON response...")
168
+ api_data = response.json()
169
+ logger.debug(f"API response data keys: {list(api_data.keys()) if isinstance(api_data, dict) else 'Not a dict'}")
170
+
171
+ # Check for API-level errors
172
+ if api_data.get("error"):
173
+ logger.error(f"API returned error: {api_data.get('message', 'No message')}")
174
+ return jsonify({
175
+ "error": "Transaction API returned an error.",
176
+ "message": api_data.get("message", "No message provided.")
177
+ }), 400
178
+
179
+ transactions = api_data.get("transactions")
180
+ logger.info(f"Transactions data type: {type(transactions)}")
181
+ logger.info(f"Number of transactions: {len(transactions) if isinstance(transactions, list) else 'N/A'}")
182
+
183
+ if transactions is None or not isinstance(transactions, list):
184
+ logger.error("Invalid transactions data format")
185
+ return jsonify({"error": "Invalid data format from transaction API. 'transactions' key is missing or not a list."}), 500
186
+
187
+ if not transactions:
188
+ logger.warning("No transactions found for profile")
189
+ return jsonify({"answer": "No transaction data was found for this profile. I can't answer any questions."})
190
+
191
+ # Convert the transaction data into a dataframe
192
+ logger.info("Converting transactions to DataFrame...")
193
+ df = pd.DataFrame(transactions)
194
+ logger.info(f"DataFrame shape: {df.shape}")
195
+ logger.info(f"DataFrame columns: {list(df.columns)}")
196
+ logger.debug(f"DataFrame head:\n{df.head()}")
197
+
198
+ # Create a SmartDataframe instance using your configuration.
199
+ logger.info("Creating SmartDataframe instance...")
200
+ pandas_agent = SmartDataframe(
201
+ df,
202
+ config={
203
+ "llm": llm,
204
+ "response_parser": FlaskResponse,
205
+ "custom_whitelisted_dependencies": [
206
+ "os", "io", "sys", "chr", "glob",
207
+ "b64decoder", "collections", "geopy",
208
+ "geopandas", "wordcloud", "builtins"
209
+ ],
210
+ "security": "none", "save_charts_path": user_defined_path,
211
+ "save_charts": False, "enable_cache": False, "conversational":True
212
+ }
213
+ )
214
+ logger.info("SmartDataframe created successfully")
215
+
216
+ # Get the answer from the agent
217
+ logger.info(f"Sending question to pandas agent: {user_question}")
218
+ answer = pandas_agent.chat(user_question)
219
+ logger.info(f"Received answer from agent, type: {type(answer)}")
220
+ logger.debug(f"Raw answer: {str(answer)[:500]}...") # Log first 500 chars
221
+
222
+ # Process the answer based on its type
223
+ logger.info("Processing answer for response format...")
224
+ formatted_answer = None
225
+ if isinstance(answer, pd.DataFrame):
226
+ logger.info("Answer is DataFrame, converting to HTML")
227
+ formatted_answer = answer.to_html()
228
+ elif isinstance(answer, plt.Figure):
229
+ logger.info("Answer is matplotlib Figure, converting to base64")
230
+ buf = io.BytesIO()
231
+ answer.savefig(buf, format="png")
232
+ buf.seek(0)
233
+ image_base64 = base64.b64encode(buf.read()).decode("utf-8")
234
+ formatted_answer = f"data:image/png;base64,{image_base64}"
235
+ else:
236
+ logger.info("Answer is other type, converting to string")
237
+ formatted_answer = str(answer)
238
+
239
+ logger.info(f"Formatted answer length: {len(str(formatted_answer))}")
240
+
241
+ # Return the formatted answer as JSON.
242
+ logger.info("Returning successful response")
243
+ return jsonify({"answer": formatted_answer})
244
+
245
+ except requests.exceptions.Timeout as e:
246
+ logger.error(f"API request timeout: {e}")
247
+ return jsonify({"error": "Transaction API request timed out.", "details": str(e)}), 504
248
+ except requests.exceptions.ConnectionError as e:
249
+ logger.error(f"API connection error: {e}")
250
+ return jsonify({"error": "Could not connect to the transaction API.", "details": str(e)}), 503
251
+ except requests.exceptions.RequestException as e:
252
+ logger.error(f"API request exception: {e}")
253
+ return jsonify({"error": "Could not connect to the transaction API.", "details": str(e)}), 500
254
+ except ValueError as e:
255
+ logger.error(f"JSON parsing error: {e}")
256
+ return jsonify({"error": "Invalid JSON response from transaction API.", "details": str(e)}), 502
257
 
 
 
258
  except Exception as e:
259
+ logger.exception("An unexpected error occurred in /chat endpoint")
260
  return jsonify({"error": "An unexpected server error occurred.", "details": str(e)}), 500
261
 
262
 
 
264
  @app.route("/report", methods=["POST"])
265
  @cross_origin()
266
  def busines_report():
267
+ logger.info("=== Starting /report endpoint ===")
268
+
269
+ try:
270
+ request_json = request.get_json()
271
+ json_data = request_json.get("json_data") if request_json else None
272
+
273
+ logger.info(f"Processing report request with data length: {len(str(json_data)) if json_data else 0}")
274
 
275
+ prompt = """
276
  You are Quantilytix business analyst. Analyze the following data and generate a comprehensive and insightful business report, including appropriate key perfomance indicators and recommendations Use markdown formatting and tables where necessary. only return the report and nothing else.
277
  data:
278
  """ + str(json_data)
279
 
280
+ logger.info("Sending request to generative model for report...")
281
+ response = model.generate_content(prompt)
282
+ report = response.text
283
+ logger.info(f"Generated report length: {len(report)}")
284
 
285
+ return jsonify(str(report))
286
+
287
+ except Exception as e:
288
+ logger.exception("Error in /report endpoint")
289
+ return jsonify({"error": "Failed to generate report.", "details": str(e)}), 500
290
 
 
291
 
292
+ # Marketing endpoint
293
  @app.route("/marketing", methods=["POST"])
294
  @cross_origin()
295
  def marketing():
296
+ logger.info("=== Starting /marketing endpoint ===")
297
+
298
+ try:
299
+ request_json = request.get_json()
300
+ json_data = request_json.get("json_data") if request_json else None
301
+
302
+ logger.info(f"Processing marketing request with data length: {len(str(json_data)) if json_data else 0}")
303
 
304
+ prompt = """
305
  You are an Quantilytix Marketing Specialist. Analyze the following data and generate a comprehensive marketing strategy, Only return the marketing strategy. be very creative:
306
  """ + str(json_data)
307
 
308
+ logger.info("Sending request to generative model for marketing strategy...")
309
+ response = model.generate_content(prompt)
310
+ report = response.text
311
+ logger.info(f"Generated marketing strategy length: {len(report)}")
312
 
313
+ return jsonify(str(report))
314
+
315
+ except Exception as e:
316
+ logger.exception("Error in /marketing endpoint")
317
+ return jsonify({"error": "Failed to generate marketing strategy.", "details": str(e)}), 500
318
 
 
319
 
320
+ # Notifications endpoint
 
 
321
  @app.route("/notify", methods=["POST"])
322
  @cross_origin()
323
  def notifications():
324
+ logger.info("=== Starting /notify endpoint ===")
325
+
326
+ try:
327
+ request_json = request.get_json()
328
+ json_data = request_json.get("json_data") if request_json else None
329
+
330
+ logger.info(f"Processing notification request with data length: {len(str(json_data)) if json_data else 0}")
331
 
332
+ prompt = """
333
  You are Quantilytix business analyst. Write a very brief analysis and marketing tips using this business data. your output should be suitable for a notification dashboard so no quips.
334
  """ + str(json_data)
335
 
336
+ logger.info("Sending request to generative model for notifications...")
337
+ response = model.generate_content(prompt)
338
+ report = response.text
339
+ logger.info(f"Generated notification content length: {len(report)}")
340
 
341
+ return jsonify(str(report))
342
+
343
+ except Exception as e:
344
+ logger.exception("Error in /notify endpoint")
345
+ return jsonify({"error": "Failed to generate notification content.", "details": str(e)}), 500
346
 
347
 
348
  if __name__ == "__main__":
349
+ logger.info("Starting Flask application...")
350
+ logger.info("Application will run on host=0.0.0.0, port=7860, debug=True")
351
  app.run(debug=True, host="0.0.0.0", port=7860)