Spaces:
Sleeping
Sleeping
Update main.py
Browse files
main.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
# app.py —
|
| 2 |
|
| 3 |
from langchain_google_genai import ChatGoogleGenerativeAI
|
| 4 |
import pandas as pd
|
|
@@ -278,7 +278,7 @@ def sanitize_answer(ans) -> str:
|
|
| 278 |
return s.strip()
|
| 279 |
|
| 280 |
# -----------------------------------------------------------------------------
|
| 281 |
-
# Analyst KPI layer (
|
| 282 |
# -----------------------------------------------------------------------------
|
| 283 |
class IrisReportEngine:
|
| 284 |
def __init__(self, transactions_data: list, llm_instance):
|
|
@@ -326,6 +326,7 @@ class IrisReportEngine:
|
|
| 326 |
def _get_primary_currency(self) -> str:
|
| 327 |
try:
|
| 328 |
if not self.df.empty and "Currency" in self.df.columns and not self.df["Currency"].mode().empty:
|
|
|
|
| 329 |
return str(self.df["Currency"].mode())
|
| 330 |
except Exception:
|
| 331 |
pass
|
|
@@ -350,8 +351,9 @@ class IrisReportEngine:
|
|
| 350 |
if prev == 0:
|
| 351 |
return "+100%" if cur > 0 else "0.0%"
|
| 352 |
return f"{((cur - prev) / prev) * 100:+.1f}%"
|
| 353 |
-
|
| 354 |
-
|
|
|
|
| 355 |
return {
|
| 356 |
"Total Revenue": f"{self.currency} {current_revenue:,.2f} ({pct_change(current_revenue, previous_revenue)})",
|
| 357 |
"Gross Profit": f"{self.currency} {current_profit:,.2f} ({pct_change(current_profit, previous_profit)})",
|
|
@@ -376,20 +378,22 @@ class IrisReportEngine:
|
|
| 376 |
product_intel = {}
|
| 377 |
if len(products_by_profit) > 1:
|
| 378 |
product_intel = {
|
| 379 |
-
"Best in Class (Most Profitable)": products_by_profit.idxmax(),
|
| 380 |
-
"Workhorse (Most Units Sold)": products_by_units.idxmax() if len(products_by_units) else "N/A",
|
| 381 |
"Underperformer (Least Profitable > 0)": (
|
| 382 |
-
products_by_profit[products_by_profit > 0].idxmin()
|
| 383 |
if not products_by_profit[products_by_profit > 0].empty else "N/A"
|
| 384 |
),
|
| 385 |
}
|
| 386 |
elif not products_by_profit.empty:
|
| 387 |
-
|
|
|
|
| 388 |
staff_intel = {}
|
| 389 |
if len(tellers_by_profit) > 1:
|
| 390 |
-
staff_intel = {"Top Performing Teller (by Profit)": tellers_by_profit.idxmax()}
|
| 391 |
elif not tellers_by_profit.empty:
|
| 392 |
-
|
|
|
|
| 393 |
return {
|
| 394 |
"Summary Period": summary_period,
|
| 395 |
"Performance Snapshot (vs. Prior Period)": headline,
|
|
@@ -492,22 +496,54 @@ def bot():
|
|
| 492 |
@cross_origin()
|
| 493 |
def busines_report():
|
| 494 |
logger.info("=== Starting /report endpoint ===")
|
| 495 |
-
|
| 496 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 497 |
|
| 498 |
@app.route("/marketing", methods=["POST"])
|
| 499 |
@cross_origin()
|
| 500 |
def marketing():
|
| 501 |
logger.info("=== Starting /marketing endpoint ===")
|
| 502 |
-
|
| 503 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 504 |
|
| 505 |
@app.route("/notify", methods=["POST"])
|
| 506 |
@cross_origin()
|
| 507 |
def notifications():
|
| 508 |
logger.info("=== Starting /notify endpoint ===")
|
| 509 |
-
|
| 510 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 511 |
|
| 512 |
# -----------------------------------------------------------------------------
|
| 513 |
# REVISED: ElevenLabs Voice Briefing Endpoints
|
|
|
|
| 1 |
+
# app.py — FIXED: Corrected JSON serialization error in IrisReportEngine
|
| 2 |
|
| 3 |
from langchain_google_genai import ChatGoogleGenerativeAI
|
| 4 |
import pandas as pd
|
|
|
|
| 278 |
return s.strip()
|
| 279 |
|
| 280 |
# -----------------------------------------------------------------------------
|
| 281 |
+
# Analyst KPI layer (MODIFIED WITH FIXES)
|
| 282 |
# -----------------------------------------------------------------------------
|
| 283 |
class IrisReportEngine:
|
| 284 |
def __init__(self, transactions_data: list, llm_instance):
|
|
|
|
| 326 |
def _get_primary_currency(self) -> str:
|
| 327 |
try:
|
| 328 |
if not self.df.empty and "Currency" in self.df.columns and not self.df["Currency"].mode().empty:
|
| 329 |
+
# FIX 1: Extract the first item from the mode() Series to prevent serialization issues.
|
| 330 |
return str(self.df["Currency"].mode())
|
| 331 |
except Exception:
|
| 332 |
pass
|
|
|
|
| 351 |
if prev == 0:
|
| 352 |
return "+100%" if cur > 0 else "0.0%"
|
| 353 |
return f"{((cur - prev) / prev) * 100:+.1f}%"
|
| 354 |
+
# FIX: Added dtype to pd.Series() to silence the FutureWarning from your traceback.
|
| 355 |
+
tx_now = int(current_df.get("Invoice_Number", pd.Series(dtype='object')).nunique()) if "Invoice_Number" in current_df.columns else int(len(current_df))
|
| 356 |
+
tx_prev = int(previous_df.get("Invoice_Number", pd.Series(dtype='object')).nunique()) if "Invoice_Number" in previous_df.columns else int(len(previous_df))
|
| 357 |
return {
|
| 358 |
"Total Revenue": f"{self.currency} {current_revenue:,.2f} ({pct_change(current_revenue, previous_revenue)})",
|
| 359 |
"Gross Profit": f"{self.currency} {current_profit:,.2f} ({pct_change(current_profit, previous_profit)})",
|
|
|
|
| 378 |
product_intel = {}
|
| 379 |
if len(products_by_profit) > 1:
|
| 380 |
product_intel = {
|
| 381 |
+
"Best in Class (Most Profitable)": str(products_by_profit.idxmax()),
|
| 382 |
+
"Workhorse (Most Units Sold)": str(products_by_units.idxmax()) if len(products_by_units) > 0 else "N/A",
|
| 383 |
"Underperformer (Least Profitable > 0)": (
|
| 384 |
+
str(products_by_profit[products_by_profit > 0].idxmin())
|
| 385 |
if not products_by_profit[products_by_profit > 0].empty else "N/A"
|
| 386 |
),
|
| 387 |
}
|
| 388 |
elif not products_by_profit.empty:
|
| 389 |
+
# FIX 2: Convert the single item from the index to a string to prevent it being a non-serializable Index object.
|
| 390 |
+
product_intel = {"Only Product Sold": str(products_by_profit.index)}
|
| 391 |
staff_intel = {}
|
| 392 |
if len(tellers_by_profit) > 1:
|
| 393 |
+
staff_intel = {"Top Performing Teller (by Profit)": str(tellers_by_profit.idxmax())}
|
| 394 |
elif not tellers_by_profit.empty:
|
| 395 |
+
# FIX 3: Convert the single item from the index to a string.
|
| 396 |
+
staff_intel = {"Only Teller": str(tellers_by_profit.index)}
|
| 397 |
return {
|
| 398 |
"Summary Period": summary_period,
|
| 399 |
"Performance Snapshot (vs. Prior Period)": headline,
|
|
|
|
| 496 |
@cross_origin()
|
| 497 |
def busines_report():
|
| 498 |
logger.info("=== Starting /report endpoint ===")
|
| 499 |
+
try:
|
| 500 |
+
request_json = request.get_json()
|
| 501 |
+
json_data = request_json.get("json_data") if request_json else None
|
| 502 |
+
prompt = (
|
| 503 |
+
"You are Quantilytix business analyst. Analyze the following data and generate a "
|
| 504 |
+
"comprehensive and insightful business report, including appropriate key perfomance "
|
| 505 |
+
"indicators and recommendations Use markdown formatting and tables where necessary. "
|
| 506 |
+
"only return the report and nothing else.\ndata:\n" + str(json_data)
|
| 507 |
+
)
|
| 508 |
+
response = model.generate_content(prompt)
|
| 509 |
+
return jsonify(str(response.text))
|
| 510 |
+
except Exception as e:
|
| 511 |
+
logger.exception("Error in /report endpoint")
|
| 512 |
+
return jsonify({"error": "Failed to generate report.", "details": str(e)}), 500
|
| 513 |
|
| 514 |
@app.route("/marketing", methods=["POST"])
|
| 515 |
@cross_origin()
|
| 516 |
def marketing():
|
| 517 |
logger.info("=== Starting /marketing endpoint ===")
|
| 518 |
+
try:
|
| 519 |
+
request_json = request.get_json()
|
| 520 |
+
json_data = request_json.get("json_data") if request_json else None
|
| 521 |
+
prompt = (
|
| 522 |
+
"You are an Quantilytix Marketing Specialist. Analyze the following data and generate "
|
| 523 |
+
"a comprehensive marketing strategy, Only return the marketing strategy. be very creative:\n" + str(json_data)
|
| 524 |
+
)
|
| 525 |
+
response = model.generate_content(prompt)
|
| 526 |
+
return jsonify(str(response.text))
|
| 527 |
+
except Exception as e:
|
| 528 |
+
logger.exception("Error in /marketing endpoint")
|
| 529 |
+
return jsonify({"error": "Failed to generate marketing strategy.", "details": str(e)}), 500
|
| 530 |
|
| 531 |
@app.route("/notify", methods=["POST"])
|
| 532 |
@cross_origin()
|
| 533 |
def notifications():
|
| 534 |
logger.info("=== Starting /notify endpoint ===")
|
| 535 |
+
try:
|
| 536 |
+
request_json = request.get_json()
|
| 537 |
+
json_data = request_json.get("json_data") if request_json else None
|
| 538 |
+
prompt = (
|
| 539 |
+
"You are Quantilytix business analyst. Write a very brief analysis and marketing tips "
|
| 540 |
+
"using this business data. your output should be suitable for a notification dashboard so no quips.\n" + str(json_data)
|
| 541 |
+
)
|
| 542 |
+
response = model.generate_content(prompt)
|
| 543 |
+
return jsonify(str(response.text))
|
| 544 |
+
except Exception as e:
|
| 545 |
+
logger.exception("Error in /notify endpoint")
|
| 546 |
+
return jsonify({"error": "Failed to generate notification content.", "details": str(e)}), 500
|
| 547 |
|
| 548 |
# -----------------------------------------------------------------------------
|
| 549 |
# REVISED: ElevenLabs Voice Briefing Endpoints
|