-
Notifications
You must be signed in to change notification settings - Fork 46
Added tool to fetch n most recent orders #265
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: release
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| import functools | ||
| import json | ||
| from datetime import datetime | ||
| from datetime import datetime, timezone | ||
|
|
||
| import httpx | ||
| import pytz | ||
|
|
@@ -13,7 +13,11 @@ | |
| ApiSuccess, | ||
| GeniusApiResponse, | ||
| ) | ||
| from app.core.config import EULER_DASHBOARD_API_URL, GENIUS_API_URL | ||
| from app.core.config import ( | ||
| EULER_DASHBOARD_API_URL, | ||
| GENIUS_API_URL, | ||
| GENIUS_LIST_ORDERS_API, | ||
| ) | ||
| from app.core.logger import logger | ||
| from app.core.transport.http_client import create_http_client | ||
|
|
||
|
|
@@ -155,6 +159,142 @@ async def _make_genius_api_request( | |
| ) | ||
|
|
||
|
|
||
| async def _make_list_orders_api_request( | ||
| params: FunctionCallParams, payload_details: dict | ||
| ) -> GeniusApiResponse: | ||
| """ | ||
| Generic helper to make requests to the Juspay List Orders API. | ||
| Returns a GeniusApiResponse object. | ||
| """ | ||
| if not euler_token: | ||
| logger.error( | ||
| "Tool Error: [list_orders_request] Juspay tool called without required euler_token." | ||
| ) | ||
| return ApiFailure( | ||
| error={"Tool Error": "[list_orders_request] Juspay tool is not configured."} | ||
| ) | ||
|
|
||
| start_time_str = params.arguments.get("startTime") | ||
| end_time_str = params.arguments.get("endTime") | ||
|
|
||
| try: | ||
| ist = pytz.timezone("Asia/Kolkata") | ||
| utc = pytz.utc | ||
| if not start_time_str: | ||
| now_ist = datetime.now(ist) | ||
| start_time_ist = now_ist.replace(hour=0, minute=0, second=0, microsecond=0) | ||
| else: | ||
| start_time_ist = ist.localize( | ||
| datetime.strptime(start_time_str, "%Y-%m-%d %H:%M:%S") | ||
| ) | ||
| start_time_utc = start_time_ist.astimezone(utc) | ||
|
|
||
| if end_time_str: | ||
| end_time_ist = ist.localize( | ||
| datetime.strptime(end_time_str, "%Y-%m-%d %H:%M:%S") | ||
| ) | ||
| else: | ||
| end_time_ist = datetime.now(ist) | ||
| end_time_utc = end_time_ist.astimezone(utc) | ||
|
|
||
| except Exception as e: | ||
| logger.error( | ||
| f"Tool Error: [list_orders_request] Error converting time for Juspay API: {e}" | ||
| ) | ||
| return ApiFailure( | ||
| error={ | ||
| "Tool Error": f" [list_orders_request] Invalid time format provided. Please use 'YYYY-MM-DD HH:MM:SS' in IST. Error: {e}" | ||
| } | ||
| ) | ||
|
|
||
| time_field = ( | ||
| "order_created_at" | ||
| if payload_details.get("domain") == "ordersELS" | ||
| else "date_created" | ||
| ) | ||
|
|
||
| end_time_str = end_time_utc.strftime("%Y-%m-%dT%H:%M:%SZ") | ||
| start_time_str = start_time_utc.strftime("%Y-%m-%dT%H:%M:%SZ") | ||
|
|
||
| end_time_dt = end_time_utc.fromisoformat(end_time_str.replace("Z", "+00:00")) | ||
| start_time_dt = start_time_utc.fromisoformat(start_time_str.replace("Z", "+00:00")) | ||
|
|
||
| if end_time_dt.tzinfo is None: | ||
| end_time_dt = end_time_dt.replace(tzinfo=timezone.utc) | ||
| if start_time_dt.tzinfo is None: | ||
| start_time_dt = start_time_dt.replace(tzinfo=timezone.utc) | ||
|
|
||
| date_from_ts = int(start_time_dt.timestamp()) | ||
| date_to_ts = int(end_time_dt.timestamp()) | ||
|
|
||
| qFilters = { | ||
| "and": { | ||
| "right": { | ||
| "field": time_field, | ||
| "condition": "LessThanEqual", | ||
| "val": str(date_to_ts), | ||
| }, | ||
| "left": { | ||
| "field": time_field, | ||
| "condition": "GreaterThanEqual", | ||
| "val": str(date_from_ts), | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| full_payload = { | ||
| **payload_details, | ||
| "filters": {"dateCreated": {"lte": end_time_str, "gte": start_time_str}}, | ||
| "qFilters": qFilters, | ||
| } | ||
| headers = { | ||
| "Content-Type": "application/json", | ||
| "x-web-logintoken": euler_token, | ||
| } | ||
|
|
||
| logger.info( | ||
| f"Requesting Juspay List Orders API with payload: {json.dumps(full_payload)}" | ||
| ) | ||
|
|
||
| try: | ||
| async with create_http_client(timeout=10.0) as client: | ||
| response = await client.post( | ||
| GENIUS_LIST_ORDERS_API, json=full_payload, headers=headers | ||
| ) | ||
| response.raise_for_status() | ||
| response_text = response.text | ||
| logger.info(f"Received Raw Juspay API text response: {response_text}") | ||
| return ApiSuccess(data=response_text) | ||
| except httpx.TimeoutException: | ||
| logger.error( | ||
| "Tool Error: [list_orders_api_request] Juspay API request timed out after 10 seconds." | ||
| ) | ||
| return ApiFailure( | ||
| error={ | ||
| "Tool Error": "[list_orders_api_request] It is taking too much time to process. Please try again." | ||
| } | ||
| ) | ||
| except httpx.HTTPStatusError as e: | ||
| logger.error( | ||
| f"Tool Error: [list_orders_api_request] HTTP error calling Juspay API: {e.response.status_code} - {e.response.text}" | ||
| ) | ||
| return ApiFailure( | ||
| error={ | ||
| "Tool Error": f" [list_orders_api_request] Juspay API error: {e.response.status_code}", | ||
| "details": e.response.text, | ||
| } | ||
| ) | ||
| except Exception as e: | ||
| logger.error( | ||
| f"Tool Error: [list_orders_api_request] Unexpected error calling Juspay API: {e}" | ||
| ) | ||
| return ApiFailure( | ||
| error={ | ||
| "Tool Error": f" [list_orders_api_request] An unexpected error occurred: {e}" | ||
| } | ||
| ) | ||
|
|
||
|
|
||
| def handle_genius_response(func): | ||
| """ | ||
| A decorator that takes a tool function, executes it, and handles the | ||
|
|
@@ -427,6 +567,20 @@ def get_success_transactional_data_by_time( | |
| return _make_genius_api_request(params, payload_details) | ||
|
|
||
|
|
||
| @handle_genius_response | ||
| def get_last_n_orders(params: FunctionCallParams): | ||
| logger.info(f"Fetching last placed order with params: {params.arguments}") | ||
|
|
||
| analytics_payload = { | ||
| "domain": "txnsELS", | ||
| "offset": 0, | ||
| "sortDimension": "order_created_at", | ||
| "limit": params.arguments.get("limit", 1), | ||
| } | ||
|
|
||
| return _make_list_orders_api_request(params, analytics_payload) | ||
|
|
||
|
Comment on lines
541
to
556
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chainFix sort field for txnsELS and validate limit.
Apply this diff: @handle_genius_response
def get_last_n_orders(params: FunctionCallParams):
- logger.info(f"Fetching last placed order with params: {params.arguments}")
+ logger.info(f"Fetching last N orders with params: {params.arguments}")
- analytics_payload = {
- "domain": "txnsELS",
- "offset": 0,
- "sortDimension": "order_created_at",
- "limit": params.arguments.get("limit", 1),
- }
+ # Validate and clamp limit
+ limit_arg = params.arguments.get("limit", 1)
+ try:
+ limit = max(1, min(100, int(limit_arg)))
+ except (TypeError, ValueError):
+ limit = 1
+
+ analytics_payload = {
+ "domain": "txnsELS",
+ "offset": 0,
+ "sortDimension": "date_created", # txnsELS uses date_created
+ "limit": limit,
+ }
return _make_list_orders_api_request(params, analytics_payload)If ec/v4/orders supports specifying sort order explicitly, add it (e.g., descending) per API contract. 🏁 Script executed: #!/bin/bash
# Search for txnsELS usage in analytics.py
rg -nP "'domain':\s*\"txnsELS\"" -C3 app/agents/voice/automatic/tools/juspay/analytics.py
# Search for all sortDimension values in analytics.py
rg -nP "'sortDimension':" -C3 app/agents/voice/automatic/tools/juspay/analytics.py
# Check for date_created occurrences
rg -nP "date_created" -C3 app/agents/voice/automatic/tools/juspay/analytics.py
# Check for order_created_at occurrences
rg -nP "order_created_at" -C3 app/agents/voice/automatic/tools/juspay/analytics.pyLength of output: 612 Fix sorting field and sanitize limit in get_last_n_orders
app/agents/voice/automatic/tools/juspay/analytics.py lines 571–582 🤖 Prompt for AI Agents |
||
|
|
||
| async def get_gmv_order_value_payment_method_wise_by_time(params: FunctionCallParams): | ||
| logger.info(f"Fetching real-time GMV with params: {params.arguments}") | ||
| payload_details = { | ||
|
|
@@ -1666,6 +1820,19 @@ async def update_euler_offer(params: FunctionCallParams): | |
| required=time_input_schema["required"], | ||
| ) | ||
|
|
||
| get_last_n_orders_function = FunctionSchema( | ||
| name="get_last_n_orders", | ||
| description="Retrieves the most recently placed orders within a specified time range, sorted by creation time in descending order.", | ||
| properties={ | ||
| **time_input_schema["properties"], | ||
| "limit": { | ||
| "type": "number", | ||
| "description": "Maximum number of recent orders to retrieve. Defaults to 1 to get the single most recent order. Increase this value to get multiple recent orders (e.g., 5 for last 5 orders, 10 for last 10 orders).", | ||
| }, | ||
| }, | ||
| required=time_input_schema["required"], | ||
| ) | ||
|
|
||
| gmv_order_value_payment_method_wise_function = FunctionSchema( | ||
| name="get_gmv_order_value_payment_method_wise_by_time", | ||
| description="Get the total Gross Merchandise Value (GMV) for each payment method within a specified time range. The results can be summed to calculate the total payment method GMV/sales. Use this to understand the revenue contribution of each payment method and the overall sales performance. Default to today if no timeframe specified.", | ||
|
|
@@ -1905,6 +2072,7 @@ async def update_euler_offer(params: FunctionCallParams): | |
| list_offers_by_filter_function, | ||
| delete_euler_offer_function, | ||
| update_euler_offer_function, | ||
| get_last_n_orders_function, | ||
| ] | ||
| ) | ||
|
|
||
|
|
@@ -1921,4 +2089,5 @@ async def update_euler_offer(params: FunctionCallParams): | |
| "list_offers_by_filter": list_offers_by_filter, | ||
| "delete_euler_offer": delete_euler_offer, | ||
| "update_euler_offer": update_euler_offer, | ||
| "get_last_n_orders": get_last_n_orders, | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
List Orders helper: tighten time handling, error typing, and logging/redaction.
Apply this diff:
Note: Consider confirming the necessity/contract of both "filters.dateCreated" (camelCase) and "qFilters.date_created" for ec/v4/orders; keep only the supported one to reduce ambiguity.
🏁 Script executed:
Length of output: 1353
🏁 Script executed:
Length of output: 6229
Refactor List Orders helper: microsecond truncation, error handling, logging, and headers
end_time_dt = end_time_utc.replace(microsecond=0)(and likewise for start)httpx.HTTPStatusErrorimmediately afterTimeoutException, thenhttpx.RequestErrorto avoid swallowing HTTP status errorslogger.debugwith length only to redact PII[list_orders_request]in all log messages and error payloads (remove extra spaces)User-Agentheader (e.g."User-Agent": "ClairvoyanceApp/1.0")filters.dateCreated(camelCase) andqFiltersare needed—remove the unsupported one to avoid ambiguity🧰 Tools
🪛 Ruff (0.13.1)
200-200: Do not catch blind exception:
Exception(BLE001)
287-287: Do not catch blind exception:
Exception(BLE001)
🤖 Prompt for AI Agents