rairo commited on
Commit
4d3f387
·
verified ·
1 Parent(s): c4ba0d0

Update utility.py

Browse files
Files changed (1) hide show
  1. utility.py +78 -3
utility.py CHANGED
@@ -170,7 +170,6 @@ def _analyze_image_with_vision(image_bytes: bytes) -> List[Dict]:
170
  try:
171
  image_pil = Image.open(io.BytesIO(image_bytes))
172
 
173
- # FIX 1.2, 1.3: Updated prompt with step-by-step counting instructions
174
  prompt = """
175
  You are a bookkeeping vision model. Analyze the image (receipt, invoice, handwritten note, *catalog/menu/price list*, product photo, shelf photo). Return ONLY a valid JSON array [] of transaction objects that our TEXT PIPELINE can consume directly.
176
 
@@ -551,6 +550,52 @@ class ReportEngine:
551
  }
552
  return json.dumps(self.results, indent=2)
553
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
554
  def generateResponse(prompt: str) -> str:
555
  """Generate structured JSON response from user input using Generative AI."""
556
  if not model:
@@ -981,6 +1026,9 @@ def read_datalake(user_phone: str, query: str) -> str:
981
  try:
982
  all_dfs_with_names = _fetch_all_collections_as_dfs(user_phone)
983
  if not all_dfs_with_names:
 
 
 
984
  return "You have no data recorded yet. Please add some transactions first."
985
 
986
  query_lower = query.lower()
@@ -1037,7 +1085,7 @@ def read_datalake(user_phone: str, query: str) -> str:
1037
  response = llm.invoke(synthesis_prompt)
1038
  return response.content
1039
 
1040
- # --- Tier 1.5: General Temporal Reports (New Tier to fix routing) ---
1041
  subjects = ["sales", "expenses"]
1042
  temporals = ["today", "yesterday", "week", "month", "year"]
1043
  if any(sub in query_lower for sub in subjects) and any(temp in query_lower for temp in temporals):
@@ -1073,7 +1121,34 @@ def read_datalake(user_phone: str, query: str) -> str:
1073
  response = llm.invoke(synthesis_prompt)
1074
  return response.content
1075
 
1076
- # --- Tier 3: Fortified PandasAI with Graceful Fallback (Default Path) ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1077
  else:
1078
  try:
1079
  logger.info(f"Handling '{query}' with the Fortified PandasAI Path.")
 
170
  try:
171
  image_pil = Image.open(io.BytesIO(image_bytes))
172
 
 
173
  prompt = """
174
  You are a bookkeeping vision model. Analyze the image (receipt, invoice, handwritten note, *catalog/menu/price list*, product photo, shelf photo). Return ONLY a valid JSON array [] of transaction objects that our TEXT PIPELINE can consume directly.
175
 
 
550
  }
551
  return json.dumps(self.results, indent=2)
552
 
553
+ def generate_business_snapshot(self) -> Dict[str, Any]:
554
+ """
555
+ NEW: Creates a high-level summary of the entire business for contextual AI coaching.
556
+ """
557
+ snapshot = {}
558
+ # Financial KPIs
559
+ sales_df = self.dfs.get('sales', pd.DataFrame())
560
+ expenses_df = self.dfs.get('expenses', pd.DataFrame())
561
+ total_revenue = sales_df['sale_total'].sum() if not sales_df.empty else 0
562
+ total_expenses = expenses_df['amount'].sum() if not expenses_df.empty else 0
563
+ net_profit = total_revenue - total_expenses
564
+ snapshot['financial_kpis'] = {
565
+ "Total Revenue": f"${total_revenue:.2f}",
566
+ "Total Expenses": f"${total_expenses:.2f}",
567
+ "Net Profit": f"${net_profit:.2f}"
568
+ }
569
+
570
+ # Inventory Overview
571
+ inventory_df = self.dfs.get('inventory', pd.DataFrame())
572
+ if not inventory_df.empty and 'item' in inventory_df.columns and 'quantity' in inventory_df.columns:
573
+ snapshot['inventory_overview'] = "\n".join(
574
+ [f"- {row['item']} ({int(row['quantity'])} units)" for _, row in inventory_df.iterrows()]
575
+ )
576
+ else:
577
+ snapshot['inventory_overview'] = "No inventory items recorded."
578
+
579
+ # Asset Register
580
+ assets_df = self.dfs.get('assets', pd.DataFrame())
581
+ if not assets_df.empty and 'name' in assets_df.columns and 'value' in assets_df.columns:
582
+ snapshot['asset_register'] = "\n".join(
583
+ [f"- {row['name']} (${row['value']:.2f})" for _, row in assets_df.iterrows()]
584
+ )
585
+ else:
586
+ snapshot['asset_register'] = "No assets recorded."
587
+
588
+ # Liabilities Ledger
589
+ liabilities_df = self.dfs.get('liabilities', pd.DataFrame())
590
+ if not liabilities_df.empty and 'creditor' in liabilities_df.columns and 'amount' in liabilities_df.columns:
591
+ snapshot['liabilities_ledger'] = "\n".join(
592
+ [f"- {row['creditor']} (${row['amount']:.2f})" for _, row in liabilities_df.iterrows()]
593
+ )
594
+ else:
595
+ snapshot['liabilities_ledger'] = "No liabilities recorded."
596
+
597
+ return snapshot
598
+
599
  def generateResponse(prompt: str) -> str:
600
  """Generate structured JSON response from user input using Generative AI."""
601
  if not model:
 
1026
  try:
1027
  all_dfs_with_names = _fetch_all_collections_as_dfs(user_phone)
1028
  if not all_dfs_with_names:
1029
+ # Handle no data case for the business coach
1030
+ if any(keyword in query.lower() for keyword in ['help', 'tutorial', 'guide', 'what is', 'explain']):
1031
+ return "I'm Qx, your business assistant! I can help you track sales, expenses, and inventory. Once you add some data, I can give you reports and insights. How can I help you get started?"
1032
  return "You have no data recorded yet. Please add some transactions first."
1033
 
1034
  query_lower = query.lower()
 
1085
  response = llm.invoke(synthesis_prompt)
1086
  return response.content
1087
 
1088
+ # --- Tier 1.5: General Temporal Reports ---
1089
  subjects = ["sales", "expenses"]
1090
  temporals = ["today", "yesterday", "week", "month", "year"]
1091
  if any(sub in query_lower for sub in subjects) and any(temp in query_lower for temp in temporals):
 
1121
  response = llm.invoke(synthesis_prompt)
1122
  return response.content
1123
 
1124
+ # --- Tier 3: Business Coach & Help Layer ---
1125
+ help_keywords = ['help', 'tutorial', 'guide', 'how do you work', 'what can you do', 'How can I']
1126
+ literacy_keywords = [ 'explain', 'how to', 'how do i', 'ideas for', 'advice on', "how do", ]
1127
+ if any(keyword in query_lower for keyword in help_keywords) or any(keyword in query_lower for keyword in literacy_keywords):
1128
+ logger.info(f"Handling '{query}' with the Business Coach Path.")
1129
+ snapshot = engine.generate_business_snapshot()
1130
+ snapshot_str = json.dumps(snapshot, indent=2)
1131
+
1132
+ synthesis_prompt = f"""
1133
+ You are Qx, a friendly and insightful business coach and financial expert. The user is asking a general question. Do NOT perform any calculations. Your task is to provide a clear, helpful, and strategic answer based on their question, using your general knowledge and the business snapshot provided below for context.
1134
+
1135
+ **IMPORTANT RULES:**
1136
+ 1. **Use the Context:** Use the Business Snapshot as your internal knowledge to make your advice relevant and personalized. For example, if inventory is high for an item, you might suggest a promotion. If profit is low, you might suggest cost-cutting measures.
1137
+ 2. **Do NOT State the Numbers:** Do NOT repeat the numbers or lists from the snapshot directly. Synthesize them into your advice.
1138
+ 3. **Stay in Character:** Act as a coach. Be encouraging and provide actionable advice.
1139
+ 4. **Handle 'Help' Queries:** If asked about your capabilities, explain that you can record transactions (sales, expenses, etc.) via text or images, generate detailed reports (profit, sales by item), answer questions about their data, and provide business advice.
1140
+ 5. **Format for WhatsApp:** Use *bold*, _italic_, and emojis to make your response clear and engaging.
1141
+
1142
+ **BUSINESS SNAPSHOT (INTERNAL CONTEXT ONLY):**
1143
+ {snapshot_str}
1144
+
1145
+ **User's Question:**
1146
+ "{query}"
1147
+ """
1148
+ response = llm.invoke(synthesis_prompt)
1149
+ return response.content
1150
+
1151
+ # --- Tier 4: Fortified PandasAI with Graceful Fallback (Final Path) ---
1152
  else:
1153
  try:
1154
  logger.info(f"Handling '{query}' with the Fortified PandasAI Path.")