Update utility.py
Browse files- utility.py +71 -38
utility.py
CHANGED
|
@@ -106,38 +106,76 @@ def generateResponse(prompt: str) -> str:
|
|
| 106 |
if not model:
|
| 107 |
return '{"error": "Model not available"}'
|
| 108 |
|
| 109 |
-
# ---
|
|
|
|
| 110 |
system_prompt = """
|
| 111 |
-
Analyze the user's request for business transaction management. Your goal is to extract structured information and output it as a valid JSON list.
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
"""
|
| 142 |
try:
|
| 143 |
full_prompt = [system_prompt, prompt]
|
|
@@ -180,7 +218,6 @@ def _get_canonical_info(user_phone: str, item_name: str) -> Dict[str, Any]:
|
|
| 180 |
"""
|
| 181 |
Finds the canonical version of an item using an "exact match first" hybrid approach.
|
| 182 |
"""
|
| 183 |
-
# --- CHANGE 2: "Exact Match First" Fuzzy Logic ---
|
| 184 |
inventory_ref = db.collection("users").document(user_phone).collection("inventory_and_services")
|
| 185 |
name_lower = item_name.lower().strip()
|
| 186 |
|
|
@@ -208,7 +245,6 @@ def _get_canonical_info(user_phone: str, item_name: str) -> Dict[str, Any]:
|
|
| 208 |
singular = name_lower
|
| 209 |
|
| 210 |
return {'doc': None, 'name': singular}
|
| 211 |
-
# --- END OF CHANGE 2 ---
|
| 212 |
|
| 213 |
|
| 214 |
def create_or_update_inventory_or_service_offering(user_phone: str, transaction_data: List[Dict]) -> tuple[bool, str]:
|
|
@@ -305,12 +341,10 @@ def create_sale(user_phone: str, transaction_data: List[Dict]) -> tuple[bool, st
|
|
| 305 |
elif last_selling_price is not None:
|
| 306 |
selling_price = last_selling_price
|
| 307 |
else:
|
| 308 |
-
# For services sold without a price, we can default to 0 or another value
|
| 309 |
-
# For goods, we must require a price.
|
| 310 |
if item_type == 'good':
|
| 311 |
return f"Sale failed for new item '{canonical_name}': You must specify a price for the first sale."
|
| 312 |
else:
|
| 313 |
-
selling_price = 0
|
| 314 |
|
| 315 |
if not isinstance(selling_price, (int, float)): selling_price = 0
|
| 316 |
|
|
@@ -555,7 +589,6 @@ def read_datalake(user_phone: str, query: str) -> str:
|
|
| 555 |
"""
|
| 556 |
Handles queries with contextual pruning, temporal awareness, and robust error handling.
|
| 557 |
"""
|
| 558 |
-
# --- CHANGE 3: Definitive Temporal Fix and Conversational Flag ---
|
| 559 |
try:
|
| 560 |
all_dfs_with_names = _fetch_all_collections_as_dfs(user_phone)
|
| 561 |
if not all_dfs_with_names:
|
|
@@ -588,7 +621,7 @@ def read_datalake(user_phone: str, query: str) -> str:
|
|
| 588 |
lake = SmartDatalake(datalake_dfs, config={
|
| 589 |
"llm": llm, "response_parser": FlaskResponse,
|
| 590 |
"save_charts_path": user_defined_path, "enable_cache": False,
|
| 591 |
-
"conversational": True
|
| 592 |
})
|
| 593 |
|
| 594 |
today_str = datetime.now(timezone.utc).strftime('%Y-%m-%d')
|
|
|
|
| 106 |
if not model:
|
| 107 |
return '{"error": "Model not available"}'
|
| 108 |
|
| 109 |
+
# --- CORRECTED SYSTEM PROMPT ---
|
| 110 |
+
# This is the restored, comprehensive prompt that correctly classifies multiple transaction types.
|
| 111 |
system_prompt = """
|
| 112 |
+
Analyze the user's request for business transaction management. Your goal is to extract structured information about one or more transactions and output it as a valid JSON list.
|
| 113 |
+
|
| 114 |
+
**1. Output Format:**
|
| 115 |
+
You MUST output your response as a valid JSON list `[]` containing one or more transaction objects `{}`.
|
| 116 |
+
|
| 117 |
+
**2. Transaction Object Structure:**
|
| 118 |
+
Each transaction object MUST have the following keys:
|
| 119 |
+
- `"intent"`: The user's goal (e.g., "create", "read", "update", "delete").
|
| 120 |
+
- `"transaction_type"`: The category of the transaction (e.g., "sale", "purchase", "inventory", "expense", "asset", "liability", "query", "service_offering").
|
| 121 |
+
- `"details"`: An object containing key-value pairs extracted from the request.
|
| 122 |
+
|
| 123 |
+
**3. Key Naming Conventions for the `details` Object:**
|
| 124 |
+
- **For Expenses:** Use `"amount"`, `"description"`, and `"category"`.
|
| 125 |
+
- **For Assets:** Use `"value"` for the monetary worth and `"name"` for the item's name.
|
| 126 |
+
- **For Liabilities:** Use `"amount"` and `"creditor"`.
|
| 127 |
+
- **For Sales/Inventory:** Use `"item"`, `"quantity"`, and `"price"`.
|
| 128 |
+
- **For all financial transactions:** If a currency symbol or code is present (e.g., $, £, €, ZAR, R), include a `"currency"` key.
|
| 129 |
+
|
| 130 |
+
**4. Important Rules:**
|
| 131 |
+
- **Rule for Queries:** For "read" intents or general questions, set `transaction_type` to "query" and the `details` object MUST contain a single key `"query"` with the user's full, original question as the value.
|
| 132 |
+
- **Rule for Multiple Items:** If the user's request contains multiple distinct transactions (e.g., recording an expense AND an asset), create a separate JSON object for each one within the main list.
|
| 133 |
+
- **Rule for Expense Normalization:** For "create" intents with `transaction_type` "expense", analyze the `description`. If it contains common keywords, normalize it to a single word. For example, if the description is "paid for fuel for the delivery truck", the normalized `description` in the JSON should be "fuel". If it's "office electricity bill", normalize it to "electricity".
|
| 134 |
+
|
| 135 |
+
**5. Examples:**
|
| 136 |
+
|
| 137 |
+
**Example 1: Simple Query**
|
| 138 |
+
- **Input:** "what are my assets?"
|
| 139 |
+
- **Output:**
|
| 140 |
+
[
|
| 141 |
+
{
|
| 142 |
+
"intent": "read",
|
| 143 |
+
"transaction_type": "query",
|
| 144 |
+
"details": {
|
| 145 |
+
"query": "what are my assets?"
|
| 146 |
+
}
|
| 147 |
+
}
|
| 148 |
+
]
|
| 149 |
+
|
| 150 |
+
**Example 2: Creating a Normalized Expense**
|
| 151 |
+
- **Input:** "I paid R250 for fuel for work"
|
| 152 |
+
- **Output:**
|
| 153 |
+
[
|
| 154 |
+
{
|
| 155 |
+
"intent": "create",
|
| 156 |
+
"transaction_type": "expense",
|
| 157 |
+
"details": {
|
| 158 |
+
"description": "fuel",
|
| 159 |
+
"amount": 250,
|
| 160 |
+
"currency": "R"
|
| 161 |
+
}
|
| 162 |
+
}
|
| 163 |
+
]
|
| 164 |
+
|
| 165 |
+
**Example 3: Creating an Asset**
|
| 166 |
+
- **Input:** "just bought a new company laptop for $1500"
|
| 167 |
+
- **Output:**
|
| 168 |
+
[
|
| 169 |
+
{
|
| 170 |
+
"intent": "create",
|
| 171 |
+
"transaction_type": "asset",
|
| 172 |
+
"details": {
|
| 173 |
+
"name": "new company laptop",
|
| 174 |
+
"value": 1500,
|
| 175 |
+
"currency": "$"
|
| 176 |
+
}
|
| 177 |
+
}
|
| 178 |
+
]
|
| 179 |
"""
|
| 180 |
try:
|
| 181 |
full_prompt = [system_prompt, prompt]
|
|
|
|
| 218 |
"""
|
| 219 |
Finds the canonical version of an item using an "exact match first" hybrid approach.
|
| 220 |
"""
|
|
|
|
| 221 |
inventory_ref = db.collection("users").document(user_phone).collection("inventory_and_services")
|
| 222 |
name_lower = item_name.lower().strip()
|
| 223 |
|
|
|
|
| 245 |
singular = name_lower
|
| 246 |
|
| 247 |
return {'doc': None, 'name': singular}
|
|
|
|
| 248 |
|
| 249 |
|
| 250 |
def create_or_update_inventory_or_service_offering(user_phone: str, transaction_data: List[Dict]) -> tuple[bool, str]:
|
|
|
|
| 341 |
elif last_selling_price is not None:
|
| 342 |
selling_price = last_selling_price
|
| 343 |
else:
|
|
|
|
|
|
|
| 344 |
if item_type == 'good':
|
| 345 |
return f"Sale failed for new item '{canonical_name}': You must specify a price for the first sale."
|
| 346 |
else:
|
| 347 |
+
selling_price = 0
|
| 348 |
|
| 349 |
if not isinstance(selling_price, (int, float)): selling_price = 0
|
| 350 |
|
|
|
|
| 589 |
"""
|
| 590 |
Handles queries with contextual pruning, temporal awareness, and robust error handling.
|
| 591 |
"""
|
|
|
|
| 592 |
try:
|
| 593 |
all_dfs_with_names = _fetch_all_collections_as_dfs(user_phone)
|
| 594 |
if not all_dfs_with_names:
|
|
|
|
| 621 |
lake = SmartDatalake(datalake_dfs, config={
|
| 622 |
"llm": llm, "response_parser": FlaskResponse,
|
| 623 |
"save_charts_path": user_defined_path, "enable_cache": False,
|
| 624 |
+
"conversational": True
|
| 625 |
})
|
| 626 |
|
| 627 |
today_str = datetime.now(timezone.utc).strftime('%Y-%m-%d')
|