ux: LBC Styling, feat: thinking, feat: more tools:
This commit is contained in:
+427
-2
@@ -12,12 +12,109 @@ from traderai.uex_client import UEXClient
|
||||
ToolHandler = Callable[..., Awaitable[dict[str, Any]]]
|
||||
|
||||
|
||||
UEX_GET_RESOURCES: dict[str, dict[str, Any]] = {
|
||||
"categories": {"params": ["type", "section"], "auth": False, "group": "reference"},
|
||||
"categories_attributes": {"params": ["id_category", "category_name", "category_type"], "auth": False, "group": "reference"},
|
||||
"cities": {"params": ["id", "id_planet", "id_star_system", "name", "slug"], "auth": False, "group": "locations"},
|
||||
"commodities": {"params": ["id", "name", "code", "slug"], "auth": False, "group": "trade"},
|
||||
"commodities_alerts": {"params": ["id_commodity", "commodity_name", "commodity_code", "commodity_slug"], "auth": False, "group": "trade"},
|
||||
"commodities_averages": {"params": ["id_commodity", "commodity_name", "commodity_code", "commodity_slug"], "auth": False, "group": "trade"},
|
||||
"commodities_prices": {
|
||||
"params": ["id_terminal", "id_commodity", "terminal_name", "terminal_code", "terminal_slug", "commodity_name", "commodity_code", "commodity_slug"],
|
||||
"auth": False,
|
||||
"group": "trade",
|
||||
},
|
||||
"commodities_prices_all": {"params": [], "auth": False, "group": "trade", "heavy": True},
|
||||
"commodities_prices_history": {"params": ["id_commodity", "id_terminal", "commodity_name", "terminal_name"], "auth": False, "group": "trade"},
|
||||
"commodities_ranking": {"params": ["id_commodity", "commodity_name", "commodity_code", "commodity_slug"], "auth": False, "group": "trade"},
|
||||
"commodities_raw_averages": {"params": ["id_commodity", "commodity_name", "commodity_code", "commodity_slug"], "auth": False, "group": "mining"},
|
||||
"commodities_raw_prices": {"params": ["id_terminal", "id_commodity", "terminal_name", "commodity_name"], "auth": False, "group": "mining"},
|
||||
"commodities_raw_prices_all": {"params": [], "auth": False, "group": "mining", "heavy": True},
|
||||
"commodities_routes": {"params": ["id_terminal_origin", "id_terminal_destination", "id_commodity", "terminal_origin_name", "terminal_destination_name", "commodity_name"], "auth": False, "group": "trade"},
|
||||
"commodities_status": {"params": [], "auth": False, "group": "trade"},
|
||||
"companies": {"params": ["id", "name", "code"], "auth": False, "group": "reference"},
|
||||
"contacts": {"params": ["id", "name"], "auth": False, "group": "reference"},
|
||||
"contracts": {"params": ["id", "name", "slug"], "auth": False, "group": "reference"},
|
||||
"crew": {"params": ["id", "name", "slug"], "auth": False, "group": "reference"},
|
||||
"currencies_index": {"params": ["code"], "auth": False, "group": "reference"},
|
||||
"currencies_index_history": {"params": ["code"], "auth": False, "group": "reference"},
|
||||
"data_extract": {"params": ["table"], "auth": False, "group": "data"},
|
||||
"data_parameters": {"params": ["endpoint"], "auth": False, "group": "data"},
|
||||
"factions": {"params": ["id", "name", "slug"], "auth": False, "group": "reference"},
|
||||
"fleet": {"params": ["username"], "auth": False, "group": "vehicles"},
|
||||
"fuel_prices": {"params": ["id_terminal", "terminal_name", "terminal_code", "terminal_slug"], "auth": False, "group": "trade"},
|
||||
"fuel_prices_all": {"params": [], "auth": False, "group": "trade", "heavy": True},
|
||||
"game_versions": {"params": [], "auth": False, "group": "reference"},
|
||||
"items": {"params": ["id", "id_category", "name", "uuid", "slug"], "auth": False, "group": "items"},
|
||||
"items_attributes": {"params": ["id_item", "item_name", "item_slug"], "auth": False, "group": "items"},
|
||||
"items_prices": {"params": ["id_item", "id_terminal", "item_name", "terminal_name"], "auth": False, "group": "items"},
|
||||
"items_prices_all": {"params": [], "auth": False, "group": "items", "heavy": True},
|
||||
"jump_points": {"params": ["id", "name", "slug"], "auth": False, "group": "locations"},
|
||||
"jurisdictions": {"params": ["id", "name"], "auth": False, "group": "locations"},
|
||||
"marketplace_averages": {"params": ["id_item", "item_name", "item_slug"], "auth": False, "group": "marketplace"},
|
||||
"marketplace_averages_all": {"params": [], "auth": False, "group": "marketplace", "heavy": True},
|
||||
"marketplace_favorites": {"params": ["id_listing"], "auth": True, "group": "marketplace"},
|
||||
"marketplace_listings": {"params": ["id", "slug", "username"], "auth": False, "group": "marketplace"},
|
||||
"marketplace_negotiations": {"params": ["id", "id_listing", "hash"], "auth": True, "group": "marketplace"},
|
||||
"marketplace_negotiations_messages": {"params": ["hash", "id_negotiation"], "auth": True, "group": "marketplace"},
|
||||
"marketplace_prices_averages": {"params": ["id_item", "item_name", "item_slug"], "auth": False, "group": "marketplace"},
|
||||
"marketplace_prices_averages_all": {"params": [], "auth": False, "group": "marketplace", "heavy": True},
|
||||
"marketplace_prices_history": {"params": ["id_item", "id_listing", "item_name"], "auth": False, "group": "marketplace"},
|
||||
"marketplace_trends": {"params": ["id_item", "item_name", "item_slug"], "auth": False, "group": "marketplace"},
|
||||
"moons": {"params": ["id", "id_planet", "id_star_system", "name", "slug"], "auth": False, "group": "locations"},
|
||||
"orbits": {"params": ["id", "id_star_system", "name", "slug"], "auth": False, "group": "locations"},
|
||||
"orbits_distances": {"params": ["id_origin", "id_destination"], "auth": False, "group": "locations"},
|
||||
"organizations": {"params": ["sid", "name"], "auth": False, "group": "reference"},
|
||||
"outposts": {"params": ["id", "id_moon", "id_planet", "name", "slug"], "auth": False, "group": "locations"},
|
||||
"planets": {"params": ["id", "id_star_system", "name", "slug"], "auth": False, "group": "locations"},
|
||||
"poi": {"params": ["id", "id_star_system", "name", "slug"], "auth": False, "group": "locations"},
|
||||
"refineries_audits": {"params": ["id_terminal", "terminal_name"], "auth": False, "group": "mining"},
|
||||
"refineries_capacities": {"params": ["id_terminal", "terminal_name"], "auth": False, "group": "mining"},
|
||||
"refineries_methods": {"params": ["id", "name"], "auth": False, "group": "mining"},
|
||||
"refineries_yields": {"params": ["id_terminal", "id_commodity", "terminal_name", "commodity_name"], "auth": False, "group": "mining"},
|
||||
"release_notes": {"params": [], "auth": False, "group": "reference"},
|
||||
"space_stations": {"params": ["id", "id_star_system", "id_planet", "id_moon", "name", "slug"], "auth": False, "group": "locations"},
|
||||
"star_systems": {"params": ["id", "name", "code", "slug"], "auth": False, "group": "locations"},
|
||||
"terminals": {"params": ["id", "id_star_system", "name", "code", "slug"], "auth": False, "group": "locations"},
|
||||
"terminals_distances": {"params": ["id_terminal_origin", "id_terminal_destination"], "auth": False, "group": "locations"},
|
||||
"user": {"params": ["username"], "auth": False, "group": "user"},
|
||||
"user_notifications": {"params": [], "auth": True, "group": "user"},
|
||||
"user_refineries_jobs": {"params": ["id"], "auth": True, "group": "user"},
|
||||
"user_trades": {"params": ["id"], "auth": True, "group": "user"},
|
||||
"vehicles": {"params": ["id", "name", "slug", "uuid"], "auth": False, "group": "vehicles"},
|
||||
"vehicles_loaners": {"params": ["id_vehicle", "vehicle_name", "vehicle_slug"], "auth": False, "group": "vehicles"},
|
||||
"vehicles_prices": {"params": ["id_vehicle", "vehicle_name", "vehicle_slug"], "auth": False, "group": "vehicles"},
|
||||
"vehicles_purchases_prices": {"params": ["id_vehicle", "id_terminal", "vehicle_name", "terminal_name"], "auth": False, "group": "vehicles"},
|
||||
"vehicles_purchases_prices_all": {"params": [], "auth": False, "group": "vehicles", "heavy": True},
|
||||
"vehicles_rentals_prices": {"params": ["id_vehicle", "id_terminal", "vehicle_name", "terminal_name"], "auth": False, "group": "vehicles"},
|
||||
"vehicles_rentals_prices_all": {"params": [], "auth": False, "group": "vehicles", "heavy": True},
|
||||
"wallet_balance": {"params": [], "auth": True, "group": "user"},
|
||||
}
|
||||
|
||||
UEX_POST_RESOURCES = {
|
||||
"data_submit",
|
||||
"marketplace_advertise",
|
||||
"marketplace_negotiations_messages",
|
||||
"user_refineries_jobs_add",
|
||||
"user_trades_add",
|
||||
"user_trades_edit",
|
||||
"wallet_add",
|
||||
}
|
||||
|
||||
UEX_DELETE_RESOURCES = {
|
||||
"marketplace_listings",
|
||||
"user_refineries_jobs_remove",
|
||||
"user_trades_remove",
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class PendingAction:
|
||||
id: str
|
||||
label: str
|
||||
endpoint: str
|
||||
payload: dict[str, Any]
|
||||
method: str = "POST"
|
||||
|
||||
|
||||
class ToolRegistry:
|
||||
@@ -46,10 +143,23 @@ class ToolRegistry:
|
||||
"list_wake_jobs": self.list_wake_jobs,
|
||||
"check_uex_notifications": self.check_uex_notifications,
|
||||
}
|
||||
self.handlers["uex_api_catalog"] = self.uex_api_catalog
|
||||
self.handlers["uex_get"] = self.uex_get
|
||||
self.handlers["uex_draft_post"] = self.uex_draft_post
|
||||
self.handlers["uex_draft_delete"] = self.uex_draft_delete
|
||||
for resource in UEX_GET_RESOURCES:
|
||||
self.handlers[self._get_tool_name(resource)] = self._make_get_handler(resource)
|
||||
for resource in UEX_POST_RESOURCES:
|
||||
self.handlers[self._post_tool_name(resource)] = self._make_post_handler(resource)
|
||||
for resource in UEX_DELETE_RESOURCES:
|
||||
self.handlers[self._delete_tool_name(resource)] = self._make_delete_handler(resource)
|
||||
|
||||
@property
|
||||
def schemas(self) -> list[dict[str, Any]]:
|
||||
return [
|
||||
*self._uex_get_schemas(),
|
||||
*self._uex_post_schemas(),
|
||||
*self._uex_delete_schemas(),
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
@@ -239,6 +349,8 @@ class ToolRegistry:
|
||||
action = self.pending_actions.pop(action_id, None)
|
||||
if not action:
|
||||
return {"error": f"Pending action not found: {action_id}"}
|
||||
if action.method == "DELETE":
|
||||
return await self.uex.delete(action.endpoint, action.payload, authenticated=True)
|
||||
return await self.uex.post(action.endpoint, action.payload, authenticated=True)
|
||||
|
||||
async def decline(self, action_id: str) -> dict[str, Any]:
|
||||
@@ -250,11 +362,232 @@ class ToolRegistry:
|
||||
"pending_action": {
|
||||
"id": action.id,
|
||||
"label": action.label,
|
||||
"method": action.method,
|
||||
"endpoint": action.endpoint,
|
||||
"payload": action.payload,
|
||||
},
|
||||
}
|
||||
|
||||
async def uex_api_catalog(self, group: str | None = None, resource: str | None = None) -> dict[str, Any]:
|
||||
if resource:
|
||||
key = self._validate_resource(resource, UEX_GET_RESOURCES)
|
||||
info = UEX_GET_RESOURCES[key]
|
||||
return {
|
||||
"resource": key,
|
||||
"method": "GET",
|
||||
"group": info["group"],
|
||||
"authenticated": info["auth"],
|
||||
"heavy": bool(info.get("heavy")),
|
||||
"params": info["params"],
|
||||
"write_resources": {
|
||||
"post": sorted(UEX_POST_RESOURCES),
|
||||
"delete": sorted(UEX_DELETE_RESOURCES),
|
||||
},
|
||||
}
|
||||
|
||||
grouped: dict[str, list[dict[str, Any]]] = {}
|
||||
for name, info in sorted(UEX_GET_RESOURCES.items()):
|
||||
if group and info["group"] != group:
|
||||
continue
|
||||
grouped.setdefault(info["group"], []).append(
|
||||
{
|
||||
"resource": name,
|
||||
"params": info["params"],
|
||||
"auth": info["auth"],
|
||||
"heavy": bool(info.get("heavy")),
|
||||
}
|
||||
)
|
||||
return {
|
||||
"get": grouped,
|
||||
"post": sorted(UEX_POST_RESOURCES),
|
||||
"delete": sorted(UEX_DELETE_RESOURCES),
|
||||
"usage": "Call uex_get(resource, params, fields, limit, mode). Use fields and limit to keep responses small.",
|
||||
}
|
||||
|
||||
async def uex_get(
|
||||
self,
|
||||
resource: str,
|
||||
params: dict[str, Any] | None = None,
|
||||
fields: list[str] | None = None,
|
||||
search: str | None = None,
|
||||
limit: int = 10,
|
||||
offset: int = 0,
|
||||
mode: str = "summary",
|
||||
) -> dict[str, Any]:
|
||||
resource = self._validate_resource(resource, UEX_GET_RESOURCES)
|
||||
info = UEX_GET_RESOURCES[resource]
|
||||
cleaned_params = self._filter_params(params or {}, info["params"])
|
||||
response = await self.uex.get(resource, cleaned_params, authenticated=bool(info["auth"]))
|
||||
data = response.get("data")
|
||||
items = self._as_list(data)
|
||||
total = len(items)
|
||||
if search:
|
||||
needle = search.casefold()
|
||||
items = [item for item in items if needle in self._search_text(item)]
|
||||
filtered_total = len(items)
|
||||
offset = max(0, offset)
|
||||
limit = max(1, min(limit, 100))
|
||||
window = items[offset : offset + limit]
|
||||
compacted = [
|
||||
self._project_item(item, fields=fields, mode=mode)
|
||||
for item in window
|
||||
]
|
||||
return {
|
||||
"status": response.get("status"),
|
||||
"resource": resource,
|
||||
"params": cleaned_params,
|
||||
"total": total,
|
||||
"matched": filtered_total,
|
||||
"returned": len(compacted),
|
||||
"offset": offset,
|
||||
"truncated": offset + len(compacted) < filtered_total,
|
||||
"items": compacted,
|
||||
}
|
||||
|
||||
async def uex_draft_post(self, resource: str, payload: dict[str, Any], label: str | None = None) -> dict[str, Any]:
|
||||
resource = self._validate_resource(resource, UEX_POST_RESOURCES)
|
||||
return self._pending(label or f"POST {resource}", resource, payload, method="POST")
|
||||
|
||||
async def uex_draft_delete(
|
||||
self,
|
||||
resource: str,
|
||||
params: dict[str, Any] | None = None,
|
||||
label: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
resource = self._validate_resource(resource, UEX_DELETE_RESOURCES)
|
||||
return self._pending(label or f"DELETE {resource}", resource, params or {}, method="DELETE")
|
||||
|
||||
def _make_get_handler(self, resource: str) -> ToolHandler:
|
||||
async def handler(**arguments: Any) -> dict[str, Any]:
|
||||
fields = arguments.pop("fields", None)
|
||||
search = arguments.pop("search", None)
|
||||
limit = arguments.pop("limit", 10)
|
||||
offset = arguments.pop("offset", 0)
|
||||
mode = arguments.pop("mode", "summary")
|
||||
return await self.uex_get(
|
||||
resource,
|
||||
params=arguments,
|
||||
fields=fields,
|
||||
search=search,
|
||||
limit=limit,
|
||||
offset=offset,
|
||||
mode=mode,
|
||||
)
|
||||
|
||||
return handler
|
||||
|
||||
def _make_post_handler(self, resource: str) -> ToolHandler:
|
||||
async def handler(payload: dict[str, Any], label: str | None = None) -> dict[str, Any]:
|
||||
return await self.uex_draft_post(resource, payload, label=label)
|
||||
|
||||
return handler
|
||||
|
||||
def _make_delete_handler(self, resource: str) -> ToolHandler:
|
||||
async def handler(label: str | None = None, **params: Any) -> dict[str, Any]:
|
||||
return await self.uex_draft_delete(resource, params, label=label)
|
||||
|
||||
return handler
|
||||
|
||||
@classmethod
|
||||
def _uex_get_schemas(cls) -> list[dict[str, Any]]:
|
||||
return [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": cls._get_tool_name(resource),
|
||||
"description": cls._get_tool_description(resource, info),
|
||||
"parameters": cls._get_tool_parameters(info["params"]),
|
||||
},
|
||||
}
|
||||
for resource, info in sorted(UEX_GET_RESOURCES.items())
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def _uex_post_schemas(cls) -> list[dict[str, Any]]:
|
||||
return [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": cls._post_tool_name(resource),
|
||||
"description": f"Draft UEX POST /{resource}/ for user approval. Nothing is sent until approval.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"required": ["payload"],
|
||||
"properties": {
|
||||
"payload": {"type": "object", "description": f"JSON body for UEX POST /{resource}/."},
|
||||
"label": {"type": "string", "description": "Short approval label."},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for resource in sorted(UEX_POST_RESOURCES)
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def _uex_delete_schemas(cls) -> list[dict[str, Any]]:
|
||||
return [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": cls._delete_tool_name(resource),
|
||||
"description": f"Draft UEX DELETE /{resource}/ for user approval. Nothing is deleted until approval.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {"type": "integer"},
|
||||
"label": {"type": "string", "description": "Short approval label."},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for resource in sorted(UEX_DELETE_RESOURCES)
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def _get_tool_parameters(cls, endpoint_params: list[str]) -> dict[str, Any]:
|
||||
properties = {
|
||||
param: cls._query_param_schema(param)
|
||||
for param in endpoint_params
|
||||
}
|
||||
properties.update(
|
||||
{
|
||||
"fields": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "Fields to keep in each result row.",
|
||||
},
|
||||
"search": {"type": "string", "description": "Local text filter after UEX returns data."},
|
||||
"limit": {"type": "integer", "minimum": 1, "maximum": 100, "default": 10},
|
||||
"offset": {"type": "integer", "minimum": 0, "default": 0},
|
||||
"mode": {"type": "string", "enum": ["summary", "full"], "default": "summary"},
|
||||
}
|
||||
)
|
||||
return {"type": "object", "properties": properties}
|
||||
|
||||
@staticmethod
|
||||
def _query_param_schema(param: str) -> dict[str, Any]:
|
||||
if param == "id" or param.startswith("id_"):
|
||||
return {"type": "integer"}
|
||||
return {"type": "string"}
|
||||
|
||||
@staticmethod
|
||||
def _get_tool_description(resource: str, info: dict[str, Any]) -> str:
|
||||
auth = " Authenticated." if info["auth"] else ""
|
||||
heavy = " Heavy endpoint; use fields and limit." if info.get("heavy") else ""
|
||||
return f"GET UEX /{resource}/ with compact, token-limited results.{auth}{heavy}"
|
||||
|
||||
@staticmethod
|
||||
def _get_tool_name(resource: str) -> str:
|
||||
return f"get_uex_{resource}"
|
||||
|
||||
@staticmethod
|
||||
def _post_tool_name(resource: str) -> str:
|
||||
return f"draft_uex_{resource}"
|
||||
|
||||
@staticmethod
|
||||
def _delete_tool_name(resource: str) -> str:
|
||||
return f"delete_uex_{resource}"
|
||||
|
||||
async def search_marketplace_listings(
|
||||
self,
|
||||
query: str | None = None,
|
||||
@@ -353,20 +686,112 @@ class ToolRegistry:
|
||||
pending = [item for item in notifications if not item.get("date_read")]
|
||||
return {"count": len(pending), "notifications": pending}
|
||||
|
||||
def _pending(self, label: str, endpoint: str, payload: dict[str, Any]) -> dict[str, Any]:
|
||||
def _pending(self, label: str, endpoint: str, payload: dict[str, Any], method: str = "POST") -> dict[str, Any]:
|
||||
action_id = str(uuid.uuid4())
|
||||
payload = {key: value for key, value in payload.items() if value is not None}
|
||||
self.pending_actions[action_id] = PendingAction(action_id, label, endpoint, payload)
|
||||
self.pending_actions[action_id] = PendingAction(action_id, label, endpoint, payload, method)
|
||||
return {
|
||||
"pending_action": {
|
||||
"id": action_id,
|
||||
"label": label,
|
||||
"method": method,
|
||||
"endpoint": endpoint,
|
||||
"payload": payload,
|
||||
"approval_required": self.require_write_approval,
|
||||
}
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _validate_resource(resource: str, allowed: dict[str, Any] | set[str]) -> str:
|
||||
normalized = resource.strip().strip("/").casefold()
|
||||
if normalized not in allowed:
|
||||
choices = sorted(allowed.keys() if isinstance(allowed, dict) else allowed)
|
||||
near = [name for name in choices if normalized in name or name in normalized][:8]
|
||||
hint = f" Did you mean: {', '.join(near)}?" if near else ""
|
||||
raise ValueError(f"Unsupported UEX resource: {resource}.{hint}")
|
||||
return normalized
|
||||
|
||||
@staticmethod
|
||||
def _filter_params(params: dict[str, Any], allowed_params: list[str]) -> dict[str, Any]:
|
||||
if not allowed_params:
|
||||
return {key: value for key, value in params.items() if value is not None}
|
||||
allowed = set(allowed_params)
|
||||
return {key: value for key, value in params.items() if key in allowed and value is not None}
|
||||
|
||||
@staticmethod
|
||||
def _as_list(data: Any) -> list[Any]:
|
||||
if data is None:
|
||||
return []
|
||||
if isinstance(data, list):
|
||||
return data
|
||||
return [data]
|
||||
|
||||
@classmethod
|
||||
def _project_item(cls, item: Any, fields: list[str] | None = None, mode: str = "summary") -> Any:
|
||||
if not isinstance(item, dict):
|
||||
return item
|
||||
if fields:
|
||||
return {field: cls._compact_scalar(item.get(field)) for field in fields if field in item}
|
||||
if mode == "full":
|
||||
return {key: cls._compact_scalar(value) for key, value in item.items()}
|
||||
|
||||
priority = [
|
||||
"id",
|
||||
"uuid",
|
||||
"code",
|
||||
"slug",
|
||||
"name",
|
||||
"title",
|
||||
"type",
|
||||
"section",
|
||||
"operation",
|
||||
"price",
|
||||
"currency",
|
||||
"unit",
|
||||
"location",
|
||||
"terminal_name",
|
||||
"commodity_name",
|
||||
"item_name",
|
||||
"vehicle_name",
|
||||
"price_buy",
|
||||
"price_sell",
|
||||
"scu_buy",
|
||||
"scu_sell",
|
||||
"scu_sell_stock",
|
||||
"status_buy",
|
||||
"status_sell",
|
||||
"date_modified",
|
||||
"date_added",
|
||||
]
|
||||
selected: dict[str, Any] = {}
|
||||
for key in priority:
|
||||
if key in item and item[key] not in (None, ""):
|
||||
selected[key] = cls._compact_scalar(item[key])
|
||||
for key, value in item.items():
|
||||
if len(selected) >= 16:
|
||||
break
|
||||
if key in selected or value in (None, ""):
|
||||
continue
|
||||
if isinstance(value, (str, int, float, bool)):
|
||||
selected[key] = cls._compact_scalar(value)
|
||||
return selected
|
||||
|
||||
@staticmethod
|
||||
def _compact_scalar(value: Any) -> Any:
|
||||
if isinstance(value, str) and len(value) > 240:
|
||||
return value[:237] + "..."
|
||||
if isinstance(value, list):
|
||||
return value[:5]
|
||||
if isinstance(value, dict):
|
||||
return {key: nested_value for key, nested_value in list(value.items())[:12]}
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def _search_text(cls, item: Any) -> str:
|
||||
if isinstance(item, dict):
|
||||
return " ".join(str(value) for value in item.values() if isinstance(value, (str, int, float))).casefold()
|
||||
return str(item).casefold()
|
||||
|
||||
@staticmethod
|
||||
def _summarize_listing(listing: dict[str, Any]) -> dict[str, Any]:
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user