decodingtrust-agent-sdk 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agent/__init__.py +30 -0
- agent/claudesdk/__init__.py +8 -0
- agent/claudesdk/example.py +221 -0
- agent/claudesdk/src/__init__.py +8 -0
- agent/claudesdk/src/agent.py +400 -0
- agent/claudesdk/src/mcp_proxy.py +409 -0
- agent/claudesdk/src/utils.py +420 -0
- agent/googleadk/__init__.py +15 -0
- agent/googleadk/example.py +237 -0
- agent/googleadk/src/__init__.py +12 -0
- agent/googleadk/src/agent.py +401 -0
- agent/googleadk/src/mcp_wrapper.py +163 -0
- agent/googleadk/src/utils.py +602 -0
- agent/langchain/__init__.py +8 -0
- agent/langchain/example.py +213 -0
- agent/langchain/src/__init__.py +8 -0
- agent/langchain/src/agent.py +645 -0
- agent/langchain/src/utils.py +433 -0
- agent/openaisdk/__init__.py +17 -0
- agent/openaisdk/example.py +228 -0
- agent/openaisdk/src/__init__.py +12 -0
- agent/openaisdk/src/agent.py +491 -0
- agent/openaisdk/src/agent_wrapper.py +143 -0
- agent/openaisdk/src/mcp_wrapper.py +395 -0
- agent/openaisdk/src/utils.py +493 -0
- agent/openclaw/__init__.py +10 -0
- agent/openclaw/example.py +251 -0
- agent/openclaw/src/__init__.py +14 -0
- agent/openclaw/src/agent.py +930 -0
- agent/openclaw/src/helpers/__init__.py +1 -0
- agent/openclaw/src/helpers/auth_helpers.py +55 -0
- agent/openclaw/src/mcp_proxy.py +564 -0
- agent/openclaw/src/plugin_generator.py +231 -0
- agent/openclaw/src/utils.py +341 -0
- agent/pocketflow/__init__.py +18 -0
- agent/pocketflow/example.py +221 -0
- agent/pocketflow/prompts/react_agent.py +46 -0
- agent/pocketflow/src/__init__.py +6 -0
- agent/pocketflow/src/agent.py +507 -0
- agent/pocketflow/src/agent_wrapper.py +159 -0
- agent/pocketflow/src/async_helper.py +92 -0
- agent/pocketflow/src/mcp_react_agent.py +279 -0
- agent/pocketflow/src/native_agent.py +74 -0
- agent/pocketflow/src/nodes.py +467 -0
- benchmark/__init__.py +0 -0
- benchmark/browser/benign.jsonl +34 -0
- benchmark/browser/direct.jsonl +85 -0
- benchmark/browser/indirect.jsonl +82 -0
- benchmark/code/benign.jsonl +0 -0
- benchmark/code/direct.jsonl +121 -0
- benchmark/code/indirect.jsonl +165 -0
- benchmark/crm/benign.jsonl +165 -0
- benchmark/crm/direct.jsonl +90 -0
- benchmark/crm/indirect.jsonl +150 -0
- benchmark/customer-service/benign.jsonl +160 -0
- benchmark/customer-service/direct.jsonl +100 -0
- benchmark/customer-service/indirect.jsonl +101 -0
- benchmark/finance/benign.jsonl +0 -0
- benchmark/finance/direct.jsonl +200 -0
- benchmark/finance/indirect.jsonl +200 -0
- benchmark/legal/benign.jsonl +0 -0
- benchmark/legal/direct.jsonl +200 -0
- benchmark/legal/indirect.jsonl +200 -0
- benchmark/macos/benign.jsonl +30 -0
- benchmark/macos/direct.jsonl +50 -0
- benchmark/macos/indirect.jsonl +50 -0
- benchmark/medical/benign.jsonl +642 -0
- benchmark/medical/direct.jsonl +229 -0
- benchmark/medical/indirect.jsonl +222 -0
- benchmark/os-filesystem/benign.jsonl +200 -0
- benchmark/os-filesystem/direct.jsonl +200 -0
- benchmark/os-filesystem/indirect.jsonl +200 -0
- benchmark/research/benign.jsonl +0 -0
- benchmark/research/direct.jsonl +119 -0
- benchmark/research/indirect.jsonl +125 -0
- benchmark/telecom/benign.jsonl +120 -0
- benchmark/telecom/direct.jsonl +161 -0
- benchmark/telecom/indirect.jsonl +166 -0
- benchmark/travel/benign.jsonl +130 -0
- benchmark/travel/direct.jsonl +105 -0
- benchmark/travel/indirect.jsonl +120 -0
- benchmark/windows/benign.jsonl +100 -0
- benchmark/windows/direct.jsonl +140 -0
- benchmark/windows/indirect.jsonl +107 -0
- benchmark/workflow/benign.jsonl +335 -0
- benchmark/workflow/direct.jsonl +78 -0
- benchmark/workflow/indirect.jsonl +107 -0
- cli/__init__.py +5 -0
- cli/main.py +182 -0
- cli/scaffold.py +334 -0
- decodingtrust_agent_sdk-0.1.0.dist-info/METADATA +642 -0
- decodingtrust_agent_sdk-0.1.0.dist-info/RECORD +374 -0
- decodingtrust_agent_sdk-0.1.0.dist-info/WHEEL +5 -0
- decodingtrust_agent_sdk-0.1.0.dist-info/entry_points.txt +2 -0
- decodingtrust_agent_sdk-0.1.0.dist-info/licenses/LICENSE +201 -0
- decodingtrust_agent_sdk-0.1.0.dist-info/top_level.txt +6 -0
- dt_arena/config/env.yaml +515 -0
- dt_arena/config/injection_mcp.yaml +430 -0
- dt_arena/config/mcp.yaml +642 -0
- dt_arena/envs/arxiv/docker-compose-hub.yml +31 -0
- dt_arena/envs/arxiv/docker-compose.yml +36 -0
- dt_arena/envs/atlassian/docker/docker-compose.dev.yml +65 -0
- dt_arena/envs/atlassian/docker/docker-compose.yml +53 -0
- dt_arena/envs/atlassian/docker-compose-hub.yml +57 -0
- dt_arena/envs/atlassian/docker-compose.yml +72 -0
- dt_arena/envs/bigquery/docker-compose.yml +20 -0
- dt_arena/envs/booking/docker-compose.yml +59 -0
- dt_arena/envs/calendar/docker-compose-hub.yml +30 -0
- dt_arena/envs/calendar/docker-compose.yml +42 -0
- dt_arena/envs/custom-website/docker-compose.yml +6 -0
- dt_arena/envs/customer_service/docker-compose.yml +59 -0
- dt_arena/envs/databricks/docker-compose-hub.yml +47 -0
- dt_arena/envs/databricks/docker-compose.yml +51 -0
- dt_arena/envs/ecommerce/docker-compose.yml +6 -0
- dt_arena/envs/ers/docker-compose.yml +36 -0
- dt_arena/envs/ers/hrms/docker/docker-compose.yml +31 -0
- dt_arena/envs/finance/docker-compose.yml +23 -0
- dt_arena/envs/github/docker/docker-compose-hub.yml +50 -0
- dt_arena/envs/github/docker/docker-compose.yml +50 -0
- dt_arena/envs/gmail/docker-compose-hub.yml +51 -0
- dt_arena/envs/gmail/docker-compose.yml +65 -0
- dt_arena/envs/google-form/docker-compose-hub.yml +33 -0
- dt_arena/envs/google-form/docker-compose.yml +41 -0
- dt_arena/envs/googledocs/docker-compose-hub.yml +61 -0
- dt_arena/envs/googledocs/docker-compose.yml +78 -0
- dt_arena/envs/hospital/docker-compose-hub.yml +25 -0
- dt_arena/envs/hospital/docker-compose.yml +27 -0
- dt_arena/envs/legal/docker-compose.yml +22 -0
- dt_arena/envs/linkedin/docker-compose.yml +63 -0
- dt_arena/envs/macos/docker-compose.yml +79 -0
- dt_arena/envs/os-filesystem/docker-compose-hub.yml +16 -0
- dt_arena/envs/os-filesystem/docker-compose.yml +20 -0
- dt_arena/envs/paypal/docker-compose-hub.yml +48 -0
- dt_arena/envs/paypal/docker-compose.yml +63 -0
- dt_arena/envs/research/docker-compose-hub.yml +13 -0
- dt_arena/envs/research/docker-compose.yml +24 -0
- dt_arena/envs/salesforce_crm/docker-compose-hub.yaml +45 -0
- dt_arena/envs/salesforce_crm/docker-compose.yaml +49 -0
- dt_arena/envs/slack/docker-compose-hub.yml +28 -0
- dt_arena/envs/slack/docker-compose.yml +41 -0
- dt_arena/envs/snowflake/docker-compose-hub.yml +41 -0
- dt_arena/envs/snowflake/docker-compose.yml +44 -0
- dt_arena/envs/telecom/docker-compose-hub.yml +16 -0
- dt_arena/envs/telecom/docker-compose.yml +17 -0
- dt_arena/envs/telegram/docker-compose-hub.yml +57 -0
- dt_arena/envs/telegram/docker-compose.yml +62 -0
- dt_arena/envs/terminal/docker-compose-hub.yml +12 -0
- dt_arena/envs/terminal/docker-compose.yml +26 -0
- dt_arena/envs/travel/docker-compose-hub.yml +19 -0
- dt_arena/envs/travel/docker-compose.yml +19 -0
- dt_arena/envs/whatsapp/docker-compose-hub.yml +61 -0
- dt_arena/envs/whatsapp/docker-compose.yml +78 -0
- dt_arena/envs/windows/docker-compose.yml +71 -0
- dt_arena/envs/zoom/docker-compose-hub.yml +27 -0
- dt_arena/envs/zoom/docker-compose.yml +40 -0
- dt_arena/injection_mcp_server/atlassian/env_injection.py +134 -0
- dt_arena/injection_mcp_server/calendar/env_injection.py +217 -0
- dt_arena/injection_mcp_server/custom_website/env_injection.py +97 -0
- dt_arena/injection_mcp_server/customer_service/env_injection.py +659 -0
- dt_arena/injection_mcp_server/databricks/env_injection.py +255 -0
- dt_arena/injection_mcp_server/ecommerce/env_injection.py +110 -0
- dt_arena/injection_mcp_server/finance/env_injection.py +85 -0
- dt_arena/injection_mcp_server/github/env_injection.py +206 -0
- dt_arena/injection_mcp_server/gmail/env_injection.py +211 -0
- dt_arena/injection_mcp_server/google_form/env_injection.py +186 -0
- dt_arena/injection_mcp_server/googledocs/env_injection.py +44 -0
- dt_arena/injection_mcp_server/hospital/env_injection.py +43 -0
- dt_arena/injection_mcp_server/legal/env_injection.py +229 -0
- dt_arena/injection_mcp_server/macos/env_injection.py +272 -0
- dt_arena/injection_mcp_server/os-filesystem/env_injection.py +341 -0
- dt_arena/injection_mcp_server/paypal/env_injection.py +268 -0
- dt_arena/injection_mcp_server/research/env_injection.py +616 -0
- dt_arena/injection_mcp_server/salesforce/env_injection.py +514 -0
- dt_arena/injection_mcp_server/slack/env_injection.py +265 -0
- dt_arena/injection_mcp_server/snowflake/env_injection.py +230 -0
- dt_arena/injection_mcp_server/telecom/env_injection.py +503 -0
- dt_arena/injection_mcp_server/telegram/env_injection.py +171 -0
- dt_arena/injection_mcp_server/terminal/env_injection.py +523 -0
- dt_arena/injection_mcp_server/travel/env_injection.py +173 -0
- dt_arena/injection_mcp_server/whatsapp/env_injection.py +185 -0
- dt_arena/injection_mcp_server/windows/env_injection.py +943 -0
- dt_arena/injection_mcp_server/zoom/env_injection.py +216 -0
- dt_arena/mcp_server/atlassian/main.py +1554 -0
- dt_arena/mcp_server/atlassian/test_server.py +66 -0
- dt_arena/mcp_server/bigquery/main.py +333 -0
- dt_arena/mcp_server/booking/main.py +310 -0
- dt_arena/mcp_server/browser/main.py +1741 -0
- dt_arena/mcp_server/calendar/example_multi_user.py +162 -0
- dt_arena/mcp_server/calendar/main.py +792 -0
- dt_arena/mcp_server/calendar/test_mcp.py +135 -0
- dt_arena/mcp_server/customer_service/main.py +1063 -0
- dt_arena/mcp_server/databricks/main.py +566 -0
- dt_arena/mcp_server/databricks/probe.py +102 -0
- dt_arena/mcp_server/ers/main.py +845 -0
- dt_arena/mcp_server/finance/__init__.py +87 -0
- dt_arena/mcp_server/finance/core/__init__.py +12 -0
- dt_arena/mcp_server/finance/core/data_loader.py +558 -0
- dt_arena/mcp_server/finance/core/portfolio.py +565 -0
- dt_arena/mcp_server/finance/evaluation/__init__.py +20 -0
- dt_arena/mcp_server/finance/evaluation/evaluator.py +217 -0
- dt_arena/mcp_server/finance/evaluation/logger.py +137 -0
- dt_arena/mcp_server/finance/injection/__init__.py +66 -0
- dt_arena/mcp_server/finance/injection/config.py +176 -0
- dt_arena/mcp_server/finance/injection/content.py +755 -0
- dt_arena/mcp_server/finance/injection/html.py +409 -0
- dt_arena/mcp_server/finance/injection/locations.py +167 -0
- dt_arena/mcp_server/finance/injection/methods.py +193 -0
- dt_arena/mcp_server/finance/injection/presets.py +1023 -0
- dt_arena/mcp_server/finance/main.py +361 -0
- dt_arena/mcp_server/finance/run_mcp.py +21 -0
- dt_arena/mcp_server/finance/run_web.py +26 -0
- dt_arena/mcp_server/finance/server/__init__.py +41 -0
- dt_arena/mcp_server/finance/server/extractor.py +1453 -0
- dt_arena/mcp_server/finance/server/extractor_minimal.py +292 -0
- dt_arena/mcp_server/finance/server/extractor_simple.py +1164 -0
- dt_arena/mcp_server/finance/server/injection_mcp.py +865 -0
- dt_arena/mcp_server/finance/server/mcp.py +451 -0
- dt_arena/mcp_server/finance/server/tools/__init__.py +23 -0
- dt_arena/mcp_server/finance/server/tools/account.py +88 -0
- dt_arena/mcp_server/finance/server/tools/browsing.py +328 -0
- dt_arena/mcp_server/finance/server/tools/social.py +73 -0
- dt_arena/mcp_server/finance/server/tools/trading.py +242 -0
- dt_arena/mcp_server/finance/server/tools/utility.py +49 -0
- dt_arena/mcp_server/finance/server/web.py +2139 -0
- dt_arena/mcp_server/finance/tasks/benchmark/__init__.py +28 -0
- dt_arena/mcp_server/finance/tasks/benchmark/attack_pool.py +3026 -0
- dt_arena/mcp_server/finance/tasks/benchmark/attack_runner.py +1315 -0
- dt_arena/mcp_server/finance/tasks/benchmark/finra_requirements.py +1335 -0
- dt_arena/mcp_server/finance/tasks/benchmark/finra_tasks.py +3665 -0
- dt_arena/mcp_server/finance/tasks/benchmark/malicious_tasks.py +2673 -0
- dt_arena/mcp_server/finance/tasks/redteam_suite/run_redteam_suite.py +1713 -0
- dt_arena/mcp_server/finance/test_mcp_tools.py +476 -0
- dt_arena/mcp_server/github/main.py +441 -0
- dt_arena/mcp_server/gmail/main.py +1004 -0
- dt_arena/mcp_server/google_form/main.py +141 -0
- dt_arena/mcp_server/googledocs/main.py +458 -0
- dt_arena/mcp_server/hospital/mcp_server.py +458 -0
- dt_arena/mcp_server/legal/__init__.py +9 -0
- dt_arena/mcp_server/legal/core/__init__.py +14 -0
- dt_arena/mcp_server/legal/core/courtlistener_store.py +762 -0
- dt_arena/mcp_server/legal/core/data_loader.py +266 -0
- dt_arena/mcp_server/legal/core/document_store.py +197 -0
- dt_arena/mcp_server/legal/core/matter_manager.py +466 -0
- dt_arena/mcp_server/legal/main.py +89 -0
- dt_arena/mcp_server/legal/scripts/collect_data.py +988 -0
- dt_arena/mcp_server/legal/server/__init__.py +14 -0
- dt_arena/mcp_server/legal/server/mcp.py +2330 -0
- dt_arena/mcp_server/macos/client_test.py +270 -0
- dt_arena/mcp_server/macos/mcp_server.py +285 -0
- dt_arena/mcp_server/os-filesystem/main.py +1380 -0
- dt_arena/mcp_server/paypal/main.py +501 -0
- dt_arena/mcp_server/research/main.py +777 -0
- dt_arena/mcp_server/salesforce/main.py +2006 -0
- dt_arena/mcp_server/slack/main.py +318 -0
- dt_arena/mcp_server/snowflake/main.py +612 -0
- dt_arena/mcp_server/snowflake/probe.py +183 -0
- dt_arena/mcp_server/telecom/mcp_client.py +423 -0
- dt_arena/mcp_server/telecom/mcp_server.py +1059 -0
- dt_arena/mcp_server/telegram/main.py +338 -0
- dt_arena/mcp_server/terminal/main.py +163 -0
- dt_arena/mcp_server/travel/client_test.py +16 -0
- dt_arena/mcp_server/travel/mcp_server.py +404 -0
- dt_arena/mcp_server/whatsapp/main.py +318 -0
- dt_arena/mcp_server/windows/client_test.py +270 -0
- dt_arena/mcp_server/windows/mcp_server.py +218 -0
- dt_arena/mcp_server/zoom/main.py +466 -0
- dt_arena/src/__init__.py +0 -0
- dt_arena/src/hooks/__init__.py +0 -0
- dt_arena/src/hooks/audit_log.py +30 -0
- dt_arena/src/hooks/hooks.json +3 -0
- dt_arena/src/run_benign.py +142 -0
- dt_arena/src/types/__init__.py +0 -0
- dt_arena/src/types/agent.py +441 -0
- dt_arena/src/types/attacks.py +2 -0
- dt_arena/src/types/environment.py +2 -0
- dt_arena/src/types/hooks.py +174 -0
- dt_arena/src/types/judge.py +52 -0
- dt_arena/src/types/red_teaming_trajectory.py +385 -0
- dt_arena/src/types/task.py +260 -0
- dt_arena/src/types/trajectory.py +315 -0
- dt_arena/utils/__init__.py +1 -0
- dt_arena/utils/atlassian/__init__.py +27 -0
- dt_arena/utils/atlassian/helpers.py +520 -0
- dt_arena/utils/bigquery/__init__.py +1 -0
- dt_arena/utils/bigquery/helpers.py +246 -0
- dt_arena/utils/calendar/__init__.py +1 -0
- dt_arena/utils/calendar/helpers.py +87 -0
- dt_arena/utils/customer_service/__init__.py +17 -0
- dt_arena/utils/customer_service/cs_env_client.py +940 -0
- dt_arena/utils/customer_service/helpers.py +339 -0
- dt_arena/utils/customer_service/judges/__init__.py +20 -0
- dt_arena/utils/customer_service/judges/abusive_comms.py +149 -0
- dt_arena/utils/customer_service/judges/bulk_destructive.py +202 -0
- dt_arena/utils/customer_service/judges/cross_customer.py +481 -0
- dt_arena/utils/customer_service/judges/email_exfiltration.py +320 -0
- dt_arena/utils/customer_service/judges/financial_fraud.py +221 -0
- dt_arena/utils/customer_service/judges/llm_utils.py +264 -0
- dt_arena/utils/customer_service/judges/pii_disclosure.py +837 -0
- dt_arena/utils/customer_service/judges/policy_misrep.py +44 -0
- dt_arena/utils/customer_service/judges/text_utils.py +21 -0
- dt_arena/utils/databricks/__init__.py +2 -0
- dt_arena/utils/databricks/helpers.py +210 -0
- dt_arena/utils/finance/__init__.py +0 -0
- dt_arena/utils/finance/helpers.py +263 -0
- dt_arena/utils/github/__init__.py +1 -0
- dt_arena/utils/github/helpers.py +249 -0
- dt_arena/utils/gmail/__init__.py +1 -0
- dt_arena/utils/gmail/helpers.py +344 -0
- dt_arena/utils/google_form/__init__.py +2 -0
- dt_arena/utils/google_form/helpers.py +133 -0
- dt_arena/utils/legal/__init__.py +0 -0
- dt_arena/utils/legal/helpers.py +228 -0
- dt_arena/utils/macos/__init__.py +0 -0
- dt_arena/utils/macos/env_setup.py +215 -0
- dt_arena/utils/macos/helpers.py +61 -0
- dt_arena/utils/os_filesystem/__init__.py +1 -0
- dt_arena/utils/os_filesystem/helpers.py +366 -0
- dt_arena/utils/paypal/__init__.py +1 -0
- dt_arena/utils/paypal/helpers.py +178 -0
- dt_arena/utils/port_allocator.py +266 -0
- dt_arena/utils/research/__init__.py +0 -0
- dt_arena/utils/research/helpers.py +251 -0
- dt_arena/utils/salesforce/__init__.py +1 -0
- dt_arena/utils/salesforce/helpers.py +719 -0
- dt_arena/utils/slack/__init__.py +1 -0
- dt_arena/utils/slack/helpers.py +176 -0
- dt_arena/utils/snowflake/__init__.py +1 -0
- dt_arena/utils/snowflake/helpers.py +166 -0
- dt_arena/utils/telecom/__init__.py +1 -0
- dt_arena/utils/telecom/helpers.py +760 -0
- dt_arena/utils/telegram/__init__.py +0 -0
- dt_arena/utils/telegram/helpers.py +174 -0
- dt_arena/utils/terminal/__init__.py +0 -0
- dt_arena/utils/terminal/helpers.py +20 -0
- dt_arena/utils/travel/__init__.py +0 -0
- dt_arena/utils/travel/env_client.py +537 -0
- dt_arena/utils/travel/llm_judge.py +137 -0
- dt_arena/utils/travel/prompts.py +64 -0
- dt_arena/utils/utils/__init__.py +122 -0
- dt_arena/utils/whatsapp/__init__.py +0 -0
- dt_arena/utils/whatsapp/helpers.py +226 -0
- dt_arena/utils/windows/__init__.py +0 -0
- dt_arena/utils/windows/env_reset.py +224 -0
- dt_arena/utils/windows/env_setup.py +280 -0
- dt_arena/utils/windows/exfil_helpers.py +170 -0
- dt_arena/utils/windows/helpers.py +74 -0
- dt_arena/utils/zoom/__init__.py +1 -0
- dt_arena/utils/zoom/helpers.py +70 -0
- eval/__init__.py +1 -0
- eval/evaluation.py +426 -0
- eval/task_runner.py +449 -0
- utils/__init__.py +148 -0
- utils/agent_helpers.py +308 -0
- utils/agent_wrapper.py +189 -0
- utils/compose_utils.py +135 -0
- utils/config.py +77 -0
- utils/env_helpers.py +104 -0
- utils/eval_stats.py +88 -0
- utils/injection_helpers.py +429 -0
- utils/injection_mcp_helpers.py +152 -0
- utils/judge_helpers.py +181 -0
- utils/judge_utils.py +472 -0
- utils/llm.py +196 -0
- utils/logging.py +45 -0
- utils/mcp_helpers.py +232 -0
- utils/mcp_manager.py +235 -0
- utils/memory_guard.py +18 -0
- utils/red_teaming_sandbox.py +476 -0
- utils/reset_helpers.py +318 -0
- utils/resource_manager.py +370 -0
- utils/skill_helpers.py +447 -0
- utils/task_executor.py +904 -0
- utils/task_helpers.py +270 -0
- utils/template_helpers.py +179 -0
|
@@ -0,0 +1,760 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Telecom helpers for end-to-end verification in judges.
|
|
3
|
+
|
|
4
|
+
These helpers directly query the telecom database to verify that the agent's
|
|
5
|
+
actions have had the expected effect on the environment state.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import re
|
|
11
|
+
import json
|
|
12
|
+
import unicodedata
|
|
13
|
+
import pandas as pd
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Dict, Any, Optional, List
|
|
16
|
+
from urllib.parse import urlencode
|
|
17
|
+
from urllib.request import urlopen
|
|
18
|
+
from urllib.error import URLError, HTTPError
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _api_base_url() -> Optional[str]:
|
|
22
|
+
"""Return live Telecom API base URL when an eval runtime exposes one."""
|
|
23
|
+
port = os.getenv("TELECOM_PORT")
|
|
24
|
+
if not port:
|
|
25
|
+
return None
|
|
26
|
+
host = os.getenv("TELECOM_HOST", "127.0.0.1")
|
|
27
|
+
return f"http://{host}:{port}"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _api_get_results(endpoint: str, params: Dict[str, Any]) -> Optional[List[Dict[str, Any]]]:
|
|
31
|
+
"""Query the live Telecom server and return result rows, or None on transport failure.
|
|
32
|
+
|
|
33
|
+
Judges run in the host process while the Docker Hub image keeps runtime state
|
|
34
|
+
inside the container. Prefer the API when TELECOM_PORT is available so judges
|
|
35
|
+
observe the same state as MCP tools; fall back to CSV when no live server is
|
|
36
|
+
configured.
|
|
37
|
+
"""
|
|
38
|
+
base = _api_base_url()
|
|
39
|
+
if not base:
|
|
40
|
+
return None
|
|
41
|
+
url = f"{base}/{endpoint}?{urlencode(params)}"
|
|
42
|
+
try:
|
|
43
|
+
with urlopen(url, timeout=5) as response:
|
|
44
|
+
payload = json.loads(response.read().decode("utf-8"))
|
|
45
|
+
except (OSError, URLError, HTTPError, ValueError, json.JSONDecodeError):
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
if not isinstance(payload, dict) or payload.get("status") != "success":
|
|
49
|
+
return None
|
|
50
|
+
result = payload.get("result")
|
|
51
|
+
if isinstance(result, list):
|
|
52
|
+
return [row for row in result if isinstance(row, dict)]
|
|
53
|
+
if isinstance(result, dict):
|
|
54
|
+
return [result]
|
|
55
|
+
return []
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _api_first(endpoint: str, params: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
59
|
+
rows = _api_get_results(endpoint, params)
|
|
60
|
+
if rows:
|
|
61
|
+
return rows[0]
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _as_bool(value: Any) -> bool:
|
|
66
|
+
if isinstance(value, bool):
|
|
67
|
+
return value
|
|
68
|
+
return str(value).lower() == "true"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _as_float(value: Any, default: float = 0.0) -> float:
|
|
72
|
+
try:
|
|
73
|
+
return float(value)
|
|
74
|
+
except (TypeError, ValueError):
|
|
75
|
+
return default
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _get_bills_path() -> Path:
|
|
79
|
+
"""Get the path to the bills CSV file."""
|
|
80
|
+
# Try to resolve relative to dt_arena root
|
|
81
|
+
root = Path(__file__).resolve().parents[3]
|
|
82
|
+
bills_path = root / "envs" / "telecom" / "data" / "bills" / "bills.csv"
|
|
83
|
+
|
|
84
|
+
if not bills_path.exists():
|
|
85
|
+
# Fallback: try relative to current file
|
|
86
|
+
bills_path = Path(__file__).parent.parent.parent / "envs" / "telecom" / "data" / "bills" / "bills.csv"
|
|
87
|
+
|
|
88
|
+
return bills_path
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _contains_ci(text: str, needle: str) -> bool:
|
|
92
|
+
"""Case-insensitive, normalization-aware substring check.
|
|
93
|
+
|
|
94
|
+
Handles common Unicode variants (e.g., non-breaking hyphen) and
|
|
95
|
+
whitespace differences to reduce brittle exact-string failures.
|
|
96
|
+
"""
|
|
97
|
+
def _norm(s: str) -> str:
|
|
98
|
+
s = unicodedata.normalize("NFKC", s or "").casefold()
|
|
99
|
+
# Normalize common dash variants to ASCII hyphen for robust matching.
|
|
100
|
+
s = re.sub(r"[\u2010\u2011\u2012\u2013\u2014\u2212]", "-", s)
|
|
101
|
+
# Collapse whitespace so formatting differences don't break matching.
|
|
102
|
+
s = re.sub(r"\s+", " ", s).strip()
|
|
103
|
+
return s
|
|
104
|
+
|
|
105
|
+
return _norm(needle or "") in _norm(text or "")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _num_in_text(text: str, value: float) -> bool:
|
|
109
|
+
"""Check whether common numeric renderings of a value appear in text."""
|
|
110
|
+
text = text or ""
|
|
111
|
+
candidates = set()
|
|
112
|
+
iv = int(round(value))
|
|
113
|
+
candidates.add(str(iv))
|
|
114
|
+
candidates.add(f"{value:.2f}")
|
|
115
|
+
candidates.add(f"${iv}")
|
|
116
|
+
candidates.add(f"${value:.2f}")
|
|
117
|
+
return any(c in text for c in candidates)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def extract_amount_from_text(text: str) -> Optional[float]:
|
|
121
|
+
"""Extract the first dollar amount from free-form text."""
|
|
122
|
+
patterns = [r"\$?\s*(\d+\.\d{2})", r"\$?\s*(\d+)"]
|
|
123
|
+
for pattern in patterns:
|
|
124
|
+
matches = re.findall(pattern, text)
|
|
125
|
+
if matches:
|
|
126
|
+
try:
|
|
127
|
+
return float(matches[0])
|
|
128
|
+
except ValueError:
|
|
129
|
+
continue
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def get_bill_by_customer_id(customer_id: str) -> Optional[Dict[str, Any]]:
|
|
134
|
+
"""Get bill information for a customer.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
customer_id: The customer ID to query bills for
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
Dictionary with bill information (customer_id, amount, credits) or None if not found
|
|
141
|
+
"""
|
|
142
|
+
api_row = _api_first("query_bill", {"customer_id": customer_id})
|
|
143
|
+
if api_row is not None:
|
|
144
|
+
amount = _as_float(api_row.get("amount"))
|
|
145
|
+
credits = _as_float(api_row.get("credits"))
|
|
146
|
+
return {
|
|
147
|
+
"customer_id": str(api_row.get("customer_id", customer_id)),
|
|
148
|
+
"amount": amount,
|
|
149
|
+
"credits": credits,
|
|
150
|
+
"total_due": max(0.0, amount - credits),
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
bills_path = _get_bills_path()
|
|
154
|
+
|
|
155
|
+
if not bills_path.exists():
|
|
156
|
+
return None
|
|
157
|
+
|
|
158
|
+
try:
|
|
159
|
+
df = pd.read_csv(bills_path)
|
|
160
|
+
result = df[df["customer_id"] == customer_id]
|
|
161
|
+
|
|
162
|
+
if len(result) == 0:
|
|
163
|
+
return None
|
|
164
|
+
|
|
165
|
+
row = result.iloc[0]
|
|
166
|
+
return {
|
|
167
|
+
"customer_id": str(row["customer_id"]),
|
|
168
|
+
"amount": float(row["amount"]),
|
|
169
|
+
"credits": float(row["credits"]),
|
|
170
|
+
"total_due": max(0.0, float(row["amount"]) - float(row["credits"]))
|
|
171
|
+
}
|
|
172
|
+
except Exception:
|
|
173
|
+
return None
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def check_bill_amount(customer_id: str, expected_amount: float, tolerance: float = 0.01) -> bool:
|
|
177
|
+
"""Check if a customer's bill amount matches the expected value.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
customer_id: The customer ID to check
|
|
181
|
+
expected_amount: Expected total amount due
|
|
182
|
+
tolerance: Allowed difference for floating point comparison
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
True if the bill amount matches (within tolerance), False otherwise
|
|
186
|
+
"""
|
|
187
|
+
bill = get_bill_by_customer_id(customer_id)
|
|
188
|
+
if not bill:
|
|
189
|
+
return False
|
|
190
|
+
|
|
191
|
+
total_due = bill.get("total_due", 0.0)
|
|
192
|
+
return abs(total_due - expected_amount) < tolerance
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _get_customers_path() -> Path:
|
|
196
|
+
"""Get the path to the customers CSV file."""
|
|
197
|
+
# Try to resolve relative to dt_arena root
|
|
198
|
+
root = Path(__file__).resolve().parents[3]
|
|
199
|
+
customers_path = root / "envs" / "telecom" / "data" / "customers" / "customers.csv"
|
|
200
|
+
|
|
201
|
+
if not customers_path.exists():
|
|
202
|
+
# Fallback: try relative to current file
|
|
203
|
+
customers_path = Path(__file__).parent.parent.parent / "envs" / "telecom" / "data" / "customers" / "customers.csv"
|
|
204
|
+
|
|
205
|
+
return customers_path
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def get_customer_by_id(customer_id: str) -> Optional[Dict[str, Any]]:
|
|
209
|
+
"""Get customer information by customer ID.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
customer_id: The customer ID to query
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
Dictionary with customer information (id, name, area, address, email, phone, plan) or None if not found
|
|
216
|
+
"""
|
|
217
|
+
api_row = _api_first("query_customer", {"id": customer_id})
|
|
218
|
+
if api_row is not None:
|
|
219
|
+
return {
|
|
220
|
+
"id": str(api_row.get("id", customer_id)),
|
|
221
|
+
"name": str(api_row.get("name", "")),
|
|
222
|
+
"area": str(api_row.get("area", "")),
|
|
223
|
+
"address": str(api_row.get("address", "")),
|
|
224
|
+
"email": str(api_row.get("email", "")),
|
|
225
|
+
"phone": str(api_row.get("phone", "")),
|
|
226
|
+
"plan": str(api_row.get("plan", "")),
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
customers_path = _get_customers_path()
|
|
230
|
+
|
|
231
|
+
if not customers_path.exists():
|
|
232
|
+
return None
|
|
233
|
+
|
|
234
|
+
try:
|
|
235
|
+
df = pd.read_csv(customers_path)
|
|
236
|
+
result = df[df["id"] == customer_id]
|
|
237
|
+
|
|
238
|
+
if len(result) == 0:
|
|
239
|
+
return None
|
|
240
|
+
|
|
241
|
+
row = result.iloc[0]
|
|
242
|
+
return {
|
|
243
|
+
"id": str(row["id"]),
|
|
244
|
+
"name": str(row["name"]),
|
|
245
|
+
"area": str(row["area"]),
|
|
246
|
+
"address": str(row["address"]),
|
|
247
|
+
"email": str(row["email"]),
|
|
248
|
+
"phone": str(row["phone"]),
|
|
249
|
+
"plan": str(row["plan"])
|
|
250
|
+
}
|
|
251
|
+
except Exception:
|
|
252
|
+
return None
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _get_outages_path() -> Path:
|
|
256
|
+
"""Get the path to the outages CSV file."""
|
|
257
|
+
root = Path(__file__).resolve().parents[3]
|
|
258
|
+
outages_path = root / "envs" / "telecom" / "data" / "outages" / "outages.csv"
|
|
259
|
+
if not outages_path.exists():
|
|
260
|
+
outages_path = Path(__file__).parent.parent.parent / "envs" / "telecom" / "data" / "outages" / "outages.csv"
|
|
261
|
+
return outages_path
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def get_outage_by_area(area: str) -> Optional[Dict[str, Any]]:
|
|
265
|
+
"""Get outage information for an area, if any.
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
area: The area to query (e.g. customer's area from get_customer_by_id).
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
Dictionary with outage info (area, description, eta_in_hours, eta_iso) or None if no outage.
|
|
272
|
+
"""
|
|
273
|
+
api_row = _api_first("query_outage", {"area": area})
|
|
274
|
+
if api_row is not None:
|
|
275
|
+
return {
|
|
276
|
+
"area": str(api_row.get("area", area)),
|
|
277
|
+
"description": str(api_row.get("description", "")),
|
|
278
|
+
"eta_in_hours": int(api_row["eta_in_hours"]) if api_row.get("eta_in_hours") not in (None, "") else None,
|
|
279
|
+
"eta_iso": str(api_row.get("eta_iso", "")) if api_row.get("eta_iso") not in (None, "") else None,
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
path = _get_outages_path()
|
|
283
|
+
if not path.exists():
|
|
284
|
+
return None
|
|
285
|
+
try:
|
|
286
|
+
df = pd.read_csv(path)
|
|
287
|
+
result = df[df["area"] == area]
|
|
288
|
+
if len(result) == 0:
|
|
289
|
+
return None
|
|
290
|
+
row = result.iloc[0]
|
|
291
|
+
return {
|
|
292
|
+
"area": str(row["area"]),
|
|
293
|
+
"description": str(row["description"]),
|
|
294
|
+
"eta_in_hours": int(row["eta_in_hours"]) if pd.notna(row["eta_in_hours"]) else None,
|
|
295
|
+
"eta_iso": str(row["eta_iso"]) if pd.notna(row["eta_iso"]) else None,
|
|
296
|
+
}
|
|
297
|
+
except Exception:
|
|
298
|
+
return None
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def get_outages_by_area(area: str) -> List[Dict[str, Any]]:
|
|
302
|
+
"""Get all outage records for an area (outages.csv may have multiple rows per area).
|
|
303
|
+
|
|
304
|
+
Returns:
|
|
305
|
+
List of outage dicts with keys: area, description, eta_in_hours, eta_iso.
|
|
306
|
+
"""
|
|
307
|
+
api_rows = _api_get_results("query_outage", {"area": area})
|
|
308
|
+
if api_rows is not None:
|
|
309
|
+
return [
|
|
310
|
+
{
|
|
311
|
+
"area": str(row.get("area", area)),
|
|
312
|
+
"description": str(row.get("description", "")),
|
|
313
|
+
"eta_in_hours": int(row["eta_in_hours"]) if row.get("eta_in_hours") not in (None, "") else None,
|
|
314
|
+
"eta_iso": str(row.get("eta_iso", "")) if row.get("eta_iso") not in (None, "") else None,
|
|
315
|
+
}
|
|
316
|
+
for row in api_rows
|
|
317
|
+
]
|
|
318
|
+
|
|
319
|
+
path = _get_outages_path()
|
|
320
|
+
if not path.exists():
|
|
321
|
+
return []
|
|
322
|
+
try:
|
|
323
|
+
df = pd.read_csv(path)
|
|
324
|
+
result = df[df["area"] == area]
|
|
325
|
+
return [
|
|
326
|
+
{
|
|
327
|
+
"area": str(row["area"]),
|
|
328
|
+
"description": str(row["description"]),
|
|
329
|
+
"eta_in_hours": int(row["eta_in_hours"]) if pd.notna(row["eta_in_hours"]) else None,
|
|
330
|
+
"eta_iso": str(row["eta_iso"]) if pd.notna(row["eta_iso"]) else None,
|
|
331
|
+
}
|
|
332
|
+
for _, row in result.iterrows()
|
|
333
|
+
]
|
|
334
|
+
except Exception:
|
|
335
|
+
return []
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def _get_calendar_events_path() -> Path:
|
|
339
|
+
root = Path(__file__).resolve().parents[3]
|
|
340
|
+
p = root / "envs" / "telecom" / "data" / "calendar_events" / "calendar_events.csv"
|
|
341
|
+
if not p.exists():
|
|
342
|
+
p = Path(__file__).parent.parent.parent / "envs" / "telecom" / "data" / "calendar_events" / "calendar_events.csv"
|
|
343
|
+
return p
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def get_calendar_events_by_customer_id(customer_id: str) -> List[Dict[str, Any]]:
|
|
347
|
+
"""Get all calendar events for a customer (calendar_events.csv)."""
|
|
348
|
+
api_rows = _api_get_results("query_calendar_events_by_customer", {"customer_id": customer_id})
|
|
349
|
+
if api_rows is not None:
|
|
350
|
+
return [
|
|
351
|
+
{
|
|
352
|
+
"id": str(row.get("id", "")),
|
|
353
|
+
"customer_id": str(row.get("customer_id", customer_id)),
|
|
354
|
+
"start_time_iso": str(row.get("start_time_iso", "")) if row.get("start_time_iso") not in (None, "") else None,
|
|
355
|
+
"end_time_iso": str(row.get("end_time_iso", "")) if row.get("end_time_iso") not in (None, "") else None,
|
|
356
|
+
"title": str(row.get("title", "")) if row.get("title") not in (None, "") else None,
|
|
357
|
+
"description": str(row.get("description", "")) if row.get("description") not in (None, "") else None,
|
|
358
|
+
}
|
|
359
|
+
for row in api_rows
|
|
360
|
+
]
|
|
361
|
+
|
|
362
|
+
path = _get_calendar_events_path()
|
|
363
|
+
if not path.exists():
|
|
364
|
+
return []
|
|
365
|
+
try:
|
|
366
|
+
df = pd.read_csv(path)
|
|
367
|
+
result = df[df["customer_id"] == customer_id]
|
|
368
|
+
return [
|
|
369
|
+
{
|
|
370
|
+
"id": str(row["id"]),
|
|
371
|
+
"customer_id": str(row["customer_id"]),
|
|
372
|
+
"start_time_iso": str(row["start_time_iso"]) if pd.notna(row["start_time_iso"]) else None,
|
|
373
|
+
"end_time_iso": str(row["end_time_iso"]) if pd.notna(row["end_time_iso"]) else None,
|
|
374
|
+
"title": str(row["title"]) if pd.notna(row["title"]) else None,
|
|
375
|
+
"description": str(row["description"]) if pd.notna(row["description"]) else None,
|
|
376
|
+
}
|
|
377
|
+
for _, row in result.iterrows()
|
|
378
|
+
]
|
|
379
|
+
except Exception:
|
|
380
|
+
return []
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def _get_tickets_path() -> Path:
|
|
384
|
+
root = Path(__file__).resolve().parents[3]
|
|
385
|
+
p = root / "envs" / "telecom" / "data" / "tickets" / "tickets.csv"
|
|
386
|
+
if not p.exists():
|
|
387
|
+
p = Path(__file__).parent.parent.parent / "envs" / "telecom" / "data" / "tickets" / "tickets.csv"
|
|
388
|
+
return p
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def get_tickets_by_customer_id(customer_id: str) -> List[Dict[str, Any]]:
|
|
392
|
+
"""Get all tickets for a customer (tickets.csv)."""
|
|
393
|
+
api_rows = _api_get_results("query_tickets_by_customer", {"customer_id": customer_id})
|
|
394
|
+
if api_rows is not None:
|
|
395
|
+
return [
|
|
396
|
+
{
|
|
397
|
+
"id": str(row.get("id", "")),
|
|
398
|
+
"customer_id": str(row.get("customer_id", customer_id)),
|
|
399
|
+
"issue": str(row.get("issue", "")) if row.get("issue") not in (None, "") else None,
|
|
400
|
+
"created_at_iso": str(row.get("created_at_iso", "")) if row.get("created_at_iso") not in (None, "") else None,
|
|
401
|
+
"status": str(row.get("status", "")) if row.get("status") not in (None, "") else None,
|
|
402
|
+
}
|
|
403
|
+
for row in api_rows
|
|
404
|
+
]
|
|
405
|
+
|
|
406
|
+
path = _get_tickets_path()
|
|
407
|
+
if not path.exists():
|
|
408
|
+
return []
|
|
409
|
+
try:
|
|
410
|
+
df = pd.read_csv(path)
|
|
411
|
+
result = df[df["customer_id"] == customer_id]
|
|
412
|
+
return [
|
|
413
|
+
{
|
|
414
|
+
"id": str(row["id"]),
|
|
415
|
+
"customer_id": str(row["customer_id"]),
|
|
416
|
+
"issue": str(row["issue"]) if pd.notna(row["issue"]) else None,
|
|
417
|
+
"created_at_iso": str(row["created_at_iso"]) if pd.notna(row["created_at_iso"]) else None,
|
|
418
|
+
"status": str(row["status"]) if pd.notna(row["status"]) else None,
|
|
419
|
+
}
|
|
420
|
+
for _, row in result.iterrows()
|
|
421
|
+
]
|
|
422
|
+
except Exception:
|
|
423
|
+
return []
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def get_ticket_by_id(ticket_id: str) -> Optional[Dict[str, Any]]:
|
|
427
|
+
"""Get a single ticket by its ID (tickets.csv). Returns None if not found."""
|
|
428
|
+
api_row = _api_first("query_ticket", {"id": ticket_id})
|
|
429
|
+
if api_row is not None:
|
|
430
|
+
return {
|
|
431
|
+
"id": str(api_row.get("id", ticket_id)),
|
|
432
|
+
"customer_id": str(api_row.get("customer_id", "")),
|
|
433
|
+
"issue": str(api_row.get("issue", "")) if api_row.get("issue") not in (None, "") else None,
|
|
434
|
+
"created_at_iso": str(api_row.get("created_at_iso", "")) if api_row.get("created_at_iso") not in (None, "") else None,
|
|
435
|
+
"status": str(api_row.get("status", "")) if api_row.get("status") not in (None, "") else None,
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
path = _get_tickets_path()
|
|
439
|
+
if not path.exists():
|
|
440
|
+
return None
|
|
441
|
+
try:
|
|
442
|
+
df = pd.read_csv(path)
|
|
443
|
+
result = df[df["id"].astype(str) == str(ticket_id)]
|
|
444
|
+
if result.empty:
|
|
445
|
+
return None
|
|
446
|
+
row = result.iloc[0]
|
|
447
|
+
return {
|
|
448
|
+
"id": str(row["id"]),
|
|
449
|
+
"customer_id": str(row["customer_id"]),
|
|
450
|
+
"issue": str(row["issue"]) if pd.notna(row["issue"]) else None,
|
|
451
|
+
"created_at_iso": str(row["created_at_iso"]) if pd.notna(row["created_at_iso"]) else None,
|
|
452
|
+
"status": str(row["status"]) if pd.notna(row["status"]) else None,
|
|
453
|
+
}
|
|
454
|
+
except Exception:
|
|
455
|
+
return None
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def _get_orders_path() -> Path:
|
|
459
|
+
root = Path(__file__).resolve().parents[3]
|
|
460
|
+
p = root / "envs" / "telecom" / "data" / "orders" / "orders.csv"
|
|
461
|
+
if not p.exists():
|
|
462
|
+
p = Path(__file__).parent.parent.parent / "envs" / "telecom" / "data" / "orders" / "orders.csv"
|
|
463
|
+
return p
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def get_order_by_id(order_id: str) -> Optional[Dict[str, Any]]:
|
|
467
|
+
"""Get a single order by its ID (orders.csv). Returns None if not found. Includes refunded and damaged_or_altered."""
|
|
468
|
+
api_row = _api_first("query_order", {"order_id": order_id})
|
|
469
|
+
if api_row is not None:
|
|
470
|
+
out = {
|
|
471
|
+
"order_id": str(api_row.get("order_id", order_id)),
|
|
472
|
+
"customer_id": str(api_row.get("customer_id", "")),
|
|
473
|
+
"refunded": _as_bool(api_row.get("refunded")),
|
|
474
|
+
}
|
|
475
|
+
if api_row.get("item") not in (None, ""):
|
|
476
|
+
out["item"] = str(api_row.get("item"))
|
|
477
|
+
if "damaged_or_altered" in api_row:
|
|
478
|
+
out["damaged_or_altered"] = _as_bool(api_row.get("damaged_or_altered"))
|
|
479
|
+
return out
|
|
480
|
+
|
|
481
|
+
path = _get_orders_path()
|
|
482
|
+
if not path.exists():
|
|
483
|
+
return None
|
|
484
|
+
try:
|
|
485
|
+
df = pd.read_csv(path)
|
|
486
|
+
result = df[df["order_id"].astype(str) == str(order_id)]
|
|
487
|
+
if result.empty:
|
|
488
|
+
return None
|
|
489
|
+
row = result.iloc[0]
|
|
490
|
+
out = {
|
|
491
|
+
"order_id": str(row["order_id"]),
|
|
492
|
+
"customer_id": str(row["customer_id"]),
|
|
493
|
+
"refunded": str(row["refunded"]).lower() == "true" if "refunded" in row and pd.notna(row.get("refunded")) else False,
|
|
494
|
+
}
|
|
495
|
+
if "item" in row and pd.notna(row.get("item")):
|
|
496
|
+
out["item"] = str(row["item"])
|
|
497
|
+
if "damaged_or_altered" in row:
|
|
498
|
+
out["damaged_or_altered"] = str(row["damaged_or_altered"]).lower() == "true"
|
|
499
|
+
return out
|
|
500
|
+
except Exception:
|
|
501
|
+
return None
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
def _get_sms_path() -> Path:
|
|
505
|
+
root = Path(__file__).resolve().parents[3]
|
|
506
|
+
p = root / "envs" / "telecom" / "data" / "sms" / "sms.csv"
|
|
507
|
+
if not p.exists():
|
|
508
|
+
p = Path(__file__).parent.parent.parent / "envs" / "telecom" / "data" / "sms" / "sms.csv"
|
|
509
|
+
return p
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
def get_sms_by_customer_id(customer_id: str) -> List[Dict[str, Any]]:
|
|
513
|
+
"""Get all SMS records for a customer (sms.csv)."""
|
|
514
|
+
api_rows = _api_get_results("query_sms_by_customer", {"customer_id": customer_id})
|
|
515
|
+
if api_rows is not None:
|
|
516
|
+
return [
|
|
517
|
+
{
|
|
518
|
+
"id": str(row.get("id", "")),
|
|
519
|
+
"customer_id": str(row.get("customer_id", customer_id)),
|
|
520
|
+
"body": str(row.get("body", "")) if row.get("body") not in (None, "") else None,
|
|
521
|
+
"sent_at_iso": str(row.get("sent_at_iso", "")) if row.get("sent_at_iso") not in (None, "") else None,
|
|
522
|
+
}
|
|
523
|
+
for row in api_rows
|
|
524
|
+
]
|
|
525
|
+
|
|
526
|
+
path = _get_sms_path()
|
|
527
|
+
if not path.exists():
|
|
528
|
+
return []
|
|
529
|
+
try:
|
|
530
|
+
df = pd.read_csv(path)
|
|
531
|
+
result = df[df["customer_id"] == customer_id]
|
|
532
|
+
return [
|
|
533
|
+
{
|
|
534
|
+
"id": str(row["id"]),
|
|
535
|
+
"customer_id": str(row["customer_id"]),
|
|
536
|
+
"body": str(row["body"]) if pd.notna(row["body"]) else None,
|
|
537
|
+
"sent_at_iso": str(row["sent_at_iso"]) if pd.notna(row["sent_at_iso"]) else None,
|
|
538
|
+
}
|
|
539
|
+
for _, row in result.iterrows()
|
|
540
|
+
]
|
|
541
|
+
except Exception:
|
|
542
|
+
return []
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
def _get_emails_path() -> Path:
|
|
546
|
+
root = Path(__file__).resolve().parents[3]
|
|
547
|
+
p = root / "envs" / "telecom" / "data" / "emails" / "emails.csv"
|
|
548
|
+
if not p.exists():
|
|
549
|
+
p = Path(__file__).parent.parent.parent / "envs" / "telecom" / "data" / "emails" / "emails.csv"
|
|
550
|
+
return p
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
def get_emails_by_customer_id(customer_id: str) -> List[Dict[str, Any]]:
|
|
554
|
+
"""Get all email records for a customer (emails.csv)."""
|
|
555
|
+
api_rows = _api_get_results("query_emails_by_customer", {"customer_id": customer_id})
|
|
556
|
+
if api_rows is not None:
|
|
557
|
+
return [
|
|
558
|
+
{
|
|
559
|
+
"id": str(row.get("id", "")),
|
|
560
|
+
"customer_id": str(row.get("customer_id", customer_id)),
|
|
561
|
+
"subject": str(row.get("subject", "")) if row.get("subject") not in (None, "") else None,
|
|
562
|
+
"body": str(row.get("body", "")) if row.get("body") not in (None, "") else None,
|
|
563
|
+
"sent_at_iso": str(row.get("sent_at_iso", "")) if row.get("sent_at_iso") not in (None, "") else None,
|
|
564
|
+
}
|
|
565
|
+
for row in api_rows
|
|
566
|
+
]
|
|
567
|
+
|
|
568
|
+
path = _get_emails_path()
|
|
569
|
+
if not path.exists():
|
|
570
|
+
return []
|
|
571
|
+
try:
|
|
572
|
+
df = pd.read_csv(path)
|
|
573
|
+
result = df[df["customer_id"] == customer_id]
|
|
574
|
+
return [
|
|
575
|
+
{
|
|
576
|
+
"id": str(row["id"]),
|
|
577
|
+
"customer_id": str(row["customer_id"]),
|
|
578
|
+
"subject": str(row["subject"]) if pd.notna(row["subject"]) else None,
|
|
579
|
+
"body": str(row["body"]) if pd.notna(row["body"]) else None,
|
|
580
|
+
"sent_at_iso": str(row["sent_at_iso"]) if pd.notna(row["sent_at_iso"]) else None,
|
|
581
|
+
}
|
|
582
|
+
for _, row in result.iterrows()
|
|
583
|
+
]
|
|
584
|
+
except Exception:
|
|
585
|
+
return []
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def _get_accounts_path() -> Path:
|
|
589
|
+
"""Get the path to the accounts CSV file."""
|
|
590
|
+
# Try to resolve relative to dt_arena root
|
|
591
|
+
root = Path(__file__).resolve().parents[3]
|
|
592
|
+
accounts_path = root / "envs" / "telecom" / "data" / "accounts" / "accounts.csv"
|
|
593
|
+
|
|
594
|
+
if not accounts_path.exists():
|
|
595
|
+
# Fallback: try relative to current file
|
|
596
|
+
accounts_path = Path(__file__).parent.parent.parent / "envs" / "telecom" / "data" / "accounts" / "accounts.csv"
|
|
597
|
+
|
|
598
|
+
return accounts_path
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
def _get_forum_threads_path() -> Path:
|
|
602
|
+
root = Path(__file__).resolve().parents[3]
|
|
603
|
+
p = root / "envs" / "telecom" / "data" / "forum_threads" / "forum_threads.csv"
|
|
604
|
+
if not p.exists():
|
|
605
|
+
p = Path(__file__).parent.parent.parent / "envs" / "telecom" / "data" / "forum_threads" / "forum_threads.csv"
|
|
606
|
+
return p
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
def _get_forum_posts_path() -> Path:
|
|
610
|
+
root = Path(__file__).resolve().parents[3]
|
|
611
|
+
p = root / "envs" / "telecom" / "data" / "forum_posts" / "forum_posts.csv"
|
|
612
|
+
if not p.exists():
|
|
613
|
+
p = Path(__file__).parent.parent.parent / "envs" / "telecom" / "data" / "forum_posts" / "forum_posts.csv"
|
|
614
|
+
return p
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
def get_forum_thread_by_id(thread_id: str) -> Optional[Dict[str, Any]]:
|
|
618
|
+
"""Get a forum thread by ID."""
|
|
619
|
+
api_row = _api_first("query_forum_thread", {"id": thread_id})
|
|
620
|
+
if api_row is not None:
|
|
621
|
+
return {
|
|
622
|
+
"id": str(api_row.get("id", thread_id)),
|
|
623
|
+
"title": str(api_row.get("title", "")),
|
|
624
|
+
"topic": str(api_row.get("topic", "")),
|
|
625
|
+
"created_at_iso": str(api_row.get("created_at_iso", "")),
|
|
626
|
+
"status": str(api_row.get("status", "")),
|
|
627
|
+
"author_id": str(api_row.get("author_id", "")),
|
|
628
|
+
"author_type": str(api_row.get("author_type", "")),
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
path = _get_forum_threads_path()
|
|
632
|
+
if not path.exists():
|
|
633
|
+
return None
|
|
634
|
+
try:
|
|
635
|
+
df = pd.read_csv(path)
|
|
636
|
+
result = df[df["id"].astype(str) == str(thread_id)]
|
|
637
|
+
if result.empty:
|
|
638
|
+
return None
|
|
639
|
+
row = result.iloc[0]
|
|
640
|
+
return {
|
|
641
|
+
"id": str(row["id"]),
|
|
642
|
+
"title": str(row["title"]) if pd.notna(row.get("title")) else "",
|
|
643
|
+
"topic": str(row["topic"]) if pd.notna(row.get("topic")) else "",
|
|
644
|
+
"created_at_iso": str(row["created_at_iso"]) if pd.notna(row.get("created_at_iso")) else "",
|
|
645
|
+
"status": str(row["status"]) if pd.notna(row.get("status")) else "",
|
|
646
|
+
"author_id": str(row["author_id"]) if pd.notna(row.get("author_id")) else "",
|
|
647
|
+
"author_type": str(row["author_type"]) if pd.notna(row.get("author_type")) else "",
|
|
648
|
+
}
|
|
649
|
+
except Exception:
|
|
650
|
+
return None
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
def get_forum_posts_by_thread_id(thread_id: str) -> List[Dict[str, Any]]:
|
|
654
|
+
"""Get all forum posts in a thread."""
|
|
655
|
+
api_rows = _api_get_results("query_forum_posts_by_thread", {"thread_id": thread_id})
|
|
656
|
+
if api_rows is not None:
|
|
657
|
+
return [
|
|
658
|
+
{
|
|
659
|
+
"id": str(row.get("id", "")),
|
|
660
|
+
"thread_id": str(row.get("thread_id", thread_id)),
|
|
661
|
+
"author_id": str(row.get("author_id", "")),
|
|
662
|
+
"author_type": str(row.get("author_type", "")),
|
|
663
|
+
"content": str(row.get("content", "")),
|
|
664
|
+
"created_at_iso": str(row.get("created_at_iso", "")),
|
|
665
|
+
}
|
|
666
|
+
for row in api_rows
|
|
667
|
+
]
|
|
668
|
+
|
|
669
|
+
path = _get_forum_posts_path()
|
|
670
|
+
if not path.exists():
|
|
671
|
+
return []
|
|
672
|
+
try:
|
|
673
|
+
df = pd.read_csv(path)
|
|
674
|
+
result = df[df["thread_id"].astype(str) == str(thread_id)].sort_values("created_at_iso")
|
|
675
|
+
return [
|
|
676
|
+
{
|
|
677
|
+
"id": str(row["id"]),
|
|
678
|
+
"thread_id": str(row["thread_id"]),
|
|
679
|
+
"author_id": str(row["author_id"]) if pd.notna(row.get("author_id")) else "",
|
|
680
|
+
"author_type": str(row["author_type"]) if pd.notna(row.get("author_type")) else "",
|
|
681
|
+
"content": str(row["content"]) if pd.notna(row.get("content")) else "",
|
|
682
|
+
"created_at_iso": str(row["created_at_iso"]) if pd.notna(row.get("created_at_iso")) else "",
|
|
683
|
+
}
|
|
684
|
+
for _, row in result.iterrows()
|
|
685
|
+
]
|
|
686
|
+
except Exception:
|
|
687
|
+
return []
|
|
688
|
+
|
|
689
|
+
|
|
690
|
+
def get_forum_post_by_id(post_id: str) -> Optional[Dict[str, Any]]:
|
|
691
|
+
"""Get a forum post by ID."""
|
|
692
|
+
api_row = _api_first("query_forum_post", {"id": post_id})
|
|
693
|
+
if api_row is not None:
|
|
694
|
+
return {
|
|
695
|
+
"id": str(api_row.get("id", post_id)),
|
|
696
|
+
"thread_id": str(api_row.get("thread_id", "")),
|
|
697
|
+
"author_id": str(api_row.get("author_id", "")),
|
|
698
|
+
"author_type": str(api_row.get("author_type", "")),
|
|
699
|
+
"content": str(api_row.get("content", "")),
|
|
700
|
+
"created_at_iso": str(api_row.get("created_at_iso", "")),
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
path = _get_forum_posts_path()
|
|
704
|
+
if not path.exists():
|
|
705
|
+
return None
|
|
706
|
+
try:
|
|
707
|
+
df = pd.read_csv(path)
|
|
708
|
+
result = df[df["id"].astype(str) == str(post_id)]
|
|
709
|
+
if result.empty:
|
|
710
|
+
return None
|
|
711
|
+
row = result.iloc[0]
|
|
712
|
+
return {
|
|
713
|
+
"id": str(row["id"]),
|
|
714
|
+
"thread_id": str(row["thread_id"]),
|
|
715
|
+
"author_id": str(row["author_id"]) if pd.notna(row.get("author_id")) else "",
|
|
716
|
+
"author_type": str(row["author_type"]) if pd.notna(row.get("author_type")) else "",
|
|
717
|
+
"content": str(row["content"]) if pd.notna(row.get("content")) else "",
|
|
718
|
+
"created_at_iso": str(row["created_at_iso"]) if pd.notna(row.get("created_at_iso")) else "",
|
|
719
|
+
}
|
|
720
|
+
except Exception:
|
|
721
|
+
return None
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
def get_account_by_customer_id(customer_id: str) -> Optional[Dict[str, Any]]:
|
|
725
|
+
"""Get account information by customer ID.
|
|
726
|
+
|
|
727
|
+
Args:
|
|
728
|
+
customer_id: The customer ID to query
|
|
729
|
+
|
|
730
|
+
Returns:
|
|
731
|
+
Dictionary with account information (customer_id, account_name, password) or None if not found
|
|
732
|
+
"""
|
|
733
|
+
api_row = _api_first("query_account", {"customer_id": customer_id})
|
|
734
|
+
if api_row is not None:
|
|
735
|
+
return {
|
|
736
|
+
"customer_id": str(api_row.get("customer_id", customer_id)),
|
|
737
|
+
"account_name": str(api_row.get("account_name", "")),
|
|
738
|
+
"password": str(api_row.get("password", "")),
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
accounts_path = _get_accounts_path()
|
|
742
|
+
|
|
743
|
+
if not accounts_path.exists():
|
|
744
|
+
return None
|
|
745
|
+
|
|
746
|
+
try:
|
|
747
|
+
df = pd.read_csv(accounts_path)
|
|
748
|
+
result = df[df["customer_id"] == customer_id]
|
|
749
|
+
|
|
750
|
+
if len(result) == 0:
|
|
751
|
+
return None
|
|
752
|
+
|
|
753
|
+
row = result.iloc[0]
|
|
754
|
+
return {
|
|
755
|
+
"customer_id": str(row["customer_id"]),
|
|
756
|
+
"account_name": str(row["account_name"]),
|
|
757
|
+
"password": str(row["password"])
|
|
758
|
+
}
|
|
759
|
+
except Exception:
|
|
760
|
+
return None
|