feat(trading): support multi-item purchase intents

This commit is contained in:
2026-08-31 11:41:39 +08:00
parent 865bbe3724
commit f76c0a1518
7 changed files with 291 additions and 57 deletions
+38 -13
View File
@@ -15,7 +15,7 @@ from __future__ import annotations
from dataclasses import dataclass
from enum import StrEnum
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any
from urllib.parse import urlsplit
from app.shared.errors import AppError
@@ -55,24 +55,44 @@ def _normalize_item_url(url: str | None) -> str | None:
return f"{parts.scheme}://{parts.netloc}{parts.path.rstrip('/')}"
def _intent_item_urls(intent: dict[str, Any]) -> list[str]:
"""读取新旧意图中的商品 URL,供恢复核对使用。"""
raw_items = intent.get("items")
if raw_items is None:
raw_items = [intent]
if not isinstance(raw_items, list):
return []
urls: list[str] = []
for item in raw_items:
if isinstance(item, str):
url = item
elif isinstance(item, dict):
url = item.get("item_url")
else:
url = None
normalized = _normalize_item_url(url)
if normalized:
urls.append(normalized)
return urls
async def verify_on_site(
task: LeaseTask, *, gateway: "GatewayClient", site: "SiteInteractor"
) -> VerifyResult:
"""核对一笔任务是否已在站点上下过单
核对链路:intent.item_url → 查任务创建时间(GET /api/orders/{task_id}
LeaseTask 本身不带 created_at)→ 拉「创建时间之后」的订单列表 → 按商品 URL
比对。任何一环拿不到足够信息都返回 UNKNOWN,不猜——尤其是 NOT_ORDERED,
核对链路:intent.items(或兼容的 intent.item_url→ 查任务创建时间
(GET /api/orders/{task_id}LeaseTask 本身不带 created_at)→ 拉「创建时间之后」
的订单列表 → 按商品 URL 比对。任何一环拿不到足够信息都返回 UNKNOWN,不猜——尤其是 NOT_ORDERED,
只有在确认翻完了窗口内的全部订单后才允许返回,否则「没找到」可能只是没翻
到那一页。
"""
intent = task.intent or {}
item_url = intent.get("item_url")
if not item_url:
targets = set(_intent_item_urls(intent))
if not targets:
return VerifyResult(
VerifyVerdict.UNKNOWN, detail="intent 缺 item_url,无法比对商品"
VerifyVerdict.UNKNOWN, detail="intent 缺商品 URL(item_url/items),无法比对商品"
)
target = _normalize_item_url(item_url)
try:
task_detail = await gateway.get_task(task.task_id)
@@ -96,11 +116,16 @@ async def verify_on_site(
detail=f"订单列表查询失败:{type(exc).__name__}: {exc}",
)
matches = [
entry
for entry in window.entries
if any(_normalize_item_url(it.item_url) == target for it in entry.items)
]
matches = []
for entry in window.entries:
entry_urls = {
normalized
for normalized in (_normalize_item_url(it.item_url) for it in entry.items)
if normalized
}
# 多商品任务必须在同一笔订单中全部命中,避免部分匹配误判为已下单。
if targets.issubset(entry_urls):
matches.append(entry)
if len(matches) == 1:
return VerifyResult(
VerifyVerdict.ALREADY_ORDERED,