Skip to content

Conversation

@santhoshJus
Copy link
Contributor

@santhoshJus santhoshJus commented Sep 26, 2025

Calling /v4/orders api to fetch order level info and to call domain 'txnsELS'.
Added a tool to fetch n most recent orders along with the relevant order details

Summary by CodeRabbit

  • New Features
    • Added ability to list recent Juspay orders within a time range, with a limit and results sorted by creation time.
    • Supports IST/UTC time handling and provides clear outcomes for timeouts or HTTP errors.
  • Chores
    • Introduced configuration for the new Juspay list-orders API endpoint.

@coderabbitai
Copy link

coderabbitai bot commented Sep 26, 2025

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Introduces a new Juspay List Orders helper and tool in analytics, adds the tool schema and dispatch wiring, and adds a GENIUS_LIST_ORDERS_API config constant. The tool supports time-bounded queries, limit parameter, IST/UTC handling, and standardized success/failure responses.

Changes

Cohort / File(s) Summary
Analytics tool + wiring
app/agents/voice/automatic/tools/juspay/analytics.py
Added _make_list_orders_api_request for Juspay List Orders with IST/UTC handling and error mapping; introduced get_last_n_orders tool; added get_last_n_orders_function schema; registered tool in standard tools and dispatch map.
Config constant
app/core/config.py
Added GENIUS_LIST_ORDERS_API constant for the new list-orders endpoint.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Agent
  participant Tool as get_last_n_orders (tool)
  participant Helper as _make_list_orders_api_request
  participant Juspay as Juspay List Orders API

  Agent->>Tool: Invoke with startTime, endTime, limit
  Tool->>Helper: Build analytics payload (txnsELS, sort by order_created_at)
  Helper->>Helper: Convert times (IST/UTC), prepare headers
  Helper->>Juspay: HTTP request to GENIUS_LIST_ORDERS_API
  alt Success
    Juspay-->>Helper: 200 + orders list
    Helper-->>Tool: ApiSuccess(data)
    Tool-->>Agent: Callback with results
  else Timeout/HTTP error/Exception
    Juspay-->>Helper: Error/No response
    Helper-->>Tool: ApiFailure(error)
    Tool-->>Agent: Callback with failure
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

I thump my paws at midnight’s chime,
Fetching orders, crisp in time.
IST to UTC, I hop with flair,
Lists of loot pulled from thin air.
New endpoint shines, my whiskers twitch—
Success or fail, I’ll make the switch. 🐇📦

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title clearly states the addition of a tool for fetching the n most recent orders, which directly reflects the primary enhancement made in this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (4)
app/core/config.py (1)

91-91: Make base URL configurable (avoid hardcoding prod).

Parameterize the List Orders base URL to support non‑prod/staging and easier overrides, mirroring how EULER_DASHBOARD_API_URL is env‑driven.

Apply this diff:

+# Prefer env‑driven base so we can switch between prod/sandbox
+JUSPAY_PORTAL_BASE_URL = os.environ.get("JUSPAY_PORTAL_BASE_URL", "https://portal.juspay.in")
-GENIUS_LIST_ORDERS_API = "https://portal.juspay.in/ec/v4/orders"
+GENIUS_LIST_ORDERS_API = f"{JUSPAY_PORTAL_BASE_URL}/ec/v4/orders"
app/agents/voice/automatic/tools/juspay/analytics.py (3)

269-276: Unify error tag to [list_orders_request] and avoid blind except.

Standardize the tag for log/searchability and replace broad Exception with typed RequestError handling (diff above). This also satisfies BLE001 from Ruff.

Also applies to: 277-286, 287-295, 169-175


1823-1835: Schema: use integer for limit and add bounds/default.

Set limit to integer with a sensible default and min/max to prevent heavy calls.

Apply this diff:

 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).",
+            "type": "integer",
+            "default": 1,
+            "minimum": 1,
+            "maximum": 100,
+            "description": "Maximum number of recent orders to retrieve. Defaults to 1.",
         },
     },
     required=time_input_schema["required"],
 )

Optionally, make startTime/endTime optional for this tool to leverage sensible defaults.


3-3: Minor: pytz is fine here; prefer zoneinfo for new code.

No change required, but consider stdlib zoneinfo for new modules to avoid pytz quirks. Based on learnings.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e2755f6 and ed7dac3.

📒 Files selected for processing (2)
  • app/agents/voice/automatic/tools/juspay/analytics.py (7 hunks)
  • app/core/config.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
app/agents/voice/automatic/tools/juspay/analytics.py (2)
app/agents/voice/automatic/types/models.py (2)
  • ApiFailure (32-35)
  • ApiSuccess (25-28)
app/core/transport/http_client.py (1)
  • create_http_client (32-51)
🪛 Ruff (0.13.1)
app/agents/voice/automatic/tools/juspay/analytics.py

200-200: Do not catch blind exception: Exception

(BLE001)


287-287: Do not catch blind exception: Exception

(BLE001)

🔇 Additional comments (3)
app/agents/voice/automatic/tools/juspay/analytics.py (3)

2075-2076: Tools registration looks good.

Tool is added to the standard toolset as expected.


2092-2093: Dispatch mapping looks good.

Correctly wired to the tool function name.


210-249: Confirm filter field naming and usage
Payload merges both filters.dateCreated (ISO strings) and qFilters (snake_case order_created_at/date_created with epoch seconds). Verify which keys and formats the Juspay List Orders API actually honors and remove the unused filters to avoid silent failures.

Comment on lines 162 to 296
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}"
}
)

Copy link

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.

  • Replace string round‑trip with direct microsecond truncation; add httpx.RequestError catch; avoid logging full responses (PII risk); unify error tags; add UA header.

Apply this diff:

@@
-    time_field = (
+    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)
+    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")
+    # Drop microseconds without reparsing strings
+    end_time_dt = end_time_utc.replace(microsecond=0)
+    start_time_dt = start_time_utc.replace(microsecond=0)
@@
     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,
+        "user-agent": "ClairvoyanceApp/1.0",
     }
@@
-            response_text = response.text
-            logger.info(f"Received Raw Juspay API text response: {response_text}")
-            return ApiSuccess(data=response_text)
+            response_text = response.text
+            # Avoid logging full order payloads (may contain PII)
+            logger.debug(f"Received Juspay List Orders response (len={len(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."
-        )
+        logger.error("Tool Error: [list_orders_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."
+                "Tool Error": "[list_orders_request] It is taking too much time to process. Please try again."
             }
         )
+    except httpx.RequestError as e:
+        logger.error(f"Tool Error: [list_orders_request] Network error calling Juspay API: {e}")
+        return ApiFailure(
+            error={
+                "Tool Error": "[list_orders_request] Network error while contacting Juspay API.",
+                "details": str(e),
+            }
+        )
     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}"
-        )
+        logger.error(f"Tool Error: [list_orders_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}",
+                "Tool Error": f" [list_orders_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}"
-        )
+        logger.error(f"Tool Error: [list_orders_request] Unexpected error calling Juspay API: {e}", exc_info=True)
         return ApiFailure(
             error={
-                "Tool Error": f" [list_orders_api_request] An unexpected error occurred: {e}"
+                "Tool Error": f" [list_orders_request] An unexpected error occurred: {e}"
             }
         )

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:

#!/bin/bash
file="app/agents/voice/automatic/tools/juspay/analytics.py"
sed -n '1,50p' "$file"

Length of output: 1353


🏁 Script executed:

#!/bin/bash
file="app/agents/voice/automatic/tools/juspay/analytics.py"
grep -n "async def _make_list_orders_api_request" "$file"
sed -n '150,330p' "$file"

Length of output: 6229


Refactor List Orders helper: microsecond truncation, error handling, logging, and headers

  • Replace the round-trip ISO parse with end_time_dt = end_time_utc.replace(microsecond=0) (and likewise for start)
  • Catch httpx.HTTPStatusError immediately after TimeoutException, then httpx.RequestError to avoid swallowing HTTP status errors
  • Downgrade full response logging to logger.debug with length only to redact PII
  • Use a single tag [list_orders_request] in all log messages and error payloads (remove extra spaces)
  • Add a User-Agent header (e.g. "User-Agent": "ClairvoyanceApp/1.0")
  • Verify whether both filters.dateCreated (camelCase) and qFilters are 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
In app/agents/voice/automatic/tools/juspay/analytics.py around lines 162-296,
fix the list orders helper by: 1) replace the round-trip ISO parsing with direct
microsecond-truncated datetimes (use start_time_utc =
start_time_utc.replace(microsecond=0) and same for end_time_utc) and compute
timestamps from those; 2) reorder exception handling to catch
httpx.TimeoutException first, then httpx.HTTPStatusError immediately after, and
then httpx.RequestError/Exception to avoid swallowing status errors; 3) change
full response logging to logger.debug and log only the response length (not full
body) to avoid PII; 4) normalize all log messages and returned error payload
keys to use the single tag "[list_orders_request]" and remove extra spaces in
messages; 5) add a User-Agent header like "User-Agent": "ClairvoyanceApp/1.0" to
headers; 6) remove the unsupported/ambiguous filters.dateCreated field from
full_payload (or confirm and keep only the supported one, likely qFilters) so
only one filter mechanism is sent.

Comment on lines 571 to 582
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)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Fix sort field for txnsELS and validate limit.

  • sortDimension uses order_created_at but domain=txnsELS implies date_created; this likely breaks sorting.
  • Sanitize limit to int and clamp to a safe range.
  • Log message says “last placed order” (singular).

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.py

Length of output: 612


Fix sorting field and sanitize limit in get_last_n_orders

  • Update log to “Fetching last N orders…”
  • Replace "sortDimension": "order_created_at" with "date_created" for txnsELS
  • Cast limit to int and clamp between 1–100 (default 1)
  • Optionally add explicit sort order (e.g. "sortOrder": "desc") if supported

app/agents/voice/automatic/tools/juspay/analytics.py lines 571–582

🤖 Prompt for AI Agents
In app/agents/voice/automatic/tools/juspay/analytics.py around lines 571 to 582,
update the function to log "Fetching last N orders…" instead of the current
message; change the analytics_payload sortDimension from "order_created_at" to
"date_created" for the txnsELS domain; read params.arguments.get("limit") and
cast it to int, defaulting to 1, then clamp it to the range 1–100 before setting
analytics_payload["limit"]; and add an explicit "sortOrder": "desc" entry to
analytics_payload if the API supports it to ensure newest orders are returned
first.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant