实现租约恢复核对:verify_on_site 接真实订单列表反查
新增 SiteInteractor.list_recent_orders(分页拉 order.my.rakuten.co.jp 订单列表, 按商品 URL 反查)+ GatewayClient.get_task,替换掉恒返回 UNKNOWN 的桩。 NOT_ORDERED 分支目前只有逻辑验证、没有真实多单数据支撑,刻意仍路由到 needs_human,不自动重新下单。全程只读查询,未触发任何真实付款操作。
This commit is contained in:
@@ -4,16 +4,27 @@
|
||||
此时 worker 不能盲目重新提交(可能上次已经下单成功,只是回报断网),必须先查
|
||||
站点订单列表比对。
|
||||
|
||||
订单列表反查的实现需要实测(规格 §10 第 3 条:订单列表页能否按商品 + 时间窗口
|
||||
可靠地反查出「这单下没下」)。当前为桩:恒返回 `unknown`,让 runner 走
|
||||
needs_human 分支。**绝不**默认按「没下单」处理——那是猜,可能变成重复下单。
|
||||
2026-08-13 实现:核对逻辑用真实账号 + 真实订单验证过订单列表的数据结构(见
|
||||
app/trading/worker/site_interact.py::list_recent_orders 与 _parse_order_list 的
|
||||
模块文档),但当时账号只有 1 笔订单,ALREADY_ORDERED 分支有真实数据支撑,
|
||||
NOT_ORDERED / 多笔命中 / 多页翻页这几个分支目前只有逻辑,没有被真实多单数据
|
||||
跑过——**绝不**在没有把握时猜:任何一步信息不足都转 UNKNOWN,宁可卡住等人看
|
||||
一眼,也不赌一次重复下单。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from enum import StrEnum
|
||||
from typing import TYPE_CHECKING
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
from app.shared.errors import AppError
|
||||
from app.trading.worker.models import LeaseTask
|
||||
from app.trading.worker.site_interact import parse_order_datetime
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from app.trading.worker.client import GatewayClient
|
||||
from app.trading.worker.site_interact import SiteInteractor
|
||||
|
||||
|
||||
class VerifyVerdict(StrEnum):
|
||||
@@ -31,16 +42,85 @@ class VerifyResult:
|
||||
detail: str = ""
|
||||
|
||||
|
||||
async def verify_on_site(task: LeaseTask) -> VerifyResult:
|
||||
def _normalize_item_url(url: str | None) -> str | None:
|
||||
"""去掉查询串与末尾斜杠,只留 scheme+host+path 用于比对
|
||||
|
||||
订单列表返回的 itemUrl 实测带 `?variantId=...`(见 _parse_order_list 的真实
|
||||
样例),intent.item_url 通常不带——按查询串比对会产生假阴性,所以只比
|
||||
路径本身。
|
||||
"""
|
||||
if not url:
|
||||
return None
|
||||
parts = urlsplit(url)
|
||||
return f"{parts.scheme}://{parts.netloc}{parts.path.rstrip('/')}"
|
||||
|
||||
|
||||
async def verify_on_site(
|
||||
task: LeaseTask, *, gateway: "GatewayClient", site: "SiteInteractor"
|
||||
) -> VerifyResult:
|
||||
"""核对一笔任务是否已在站点上下过单
|
||||
|
||||
桩实现:永远返回 UNKNOWN。**绝不返回 NOT_ORDERED**——除非真实订单列表反查
|
||||
能可靠证明这一点,否则视为「无法判断」交人工,宁可卡住等人看一眼。
|
||||
|
||||
实现方需要补的实测:用 intent 里的商品 + 时间窗口(created_at 到 stale 之间)
|
||||
比对订单列表,能拿到 site_order_id 最好。
|
||||
核对链路: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:
|
||||
return VerifyResult(
|
||||
VerifyVerdict.UNKNOWN, detail="intent 缺 item_url,无法比对商品"
|
||||
)
|
||||
target = _normalize_item_url(item_url)
|
||||
|
||||
try:
|
||||
task_detail = await gateway.get_task(task.task_id)
|
||||
except AppError as exc:
|
||||
return VerifyResult(
|
||||
VerifyVerdict.UNKNOWN, detail=f"查询任务创建时间失败:{exc.message}"
|
||||
)
|
||||
|
||||
created_at_raw = task_detail.get("created_at")
|
||||
created_at = parse_order_datetime(created_at_raw) if created_at_raw else None
|
||||
if created_at is None:
|
||||
return VerifyResult(
|
||||
VerifyVerdict.UNKNOWN, detail=f"任务创建时间不可解析:{created_at_raw!r}"
|
||||
)
|
||||
|
||||
try:
|
||||
window = await site.list_recent_orders(since=created_at)
|
||||
except Exception as exc: # noqa: BLE001 — 站点交互失败一律转 unknown,不重试
|
||||
return VerifyResult(
|
||||
VerifyVerdict.UNKNOWN,
|
||||
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)
|
||||
]
|
||||
if len(matches) == 1:
|
||||
return VerifyResult(
|
||||
VerifyVerdict.ALREADY_ORDERED,
|
||||
site_order_id=matches[0].order_number,
|
||||
detail=f"订单列表命中 1 笔匹配商品的订单({matches[0].order_number})",
|
||||
)
|
||||
if len(matches) > 1:
|
||||
return VerifyResult(
|
||||
VerifyVerdict.UNKNOWN,
|
||||
detail=(
|
||||
f"订单列表命中 {len(matches)} 笔匹配商品的订单,"
|
||||
f"无法唯一确定:{[m.order_number for m in matches]}"
|
||||
),
|
||||
)
|
||||
if window.window_fully_covered:
|
||||
return VerifyResult(
|
||||
VerifyVerdict.NOT_ORDERED,
|
||||
detail="已核对任务创建时间之后的全部订单,未找到匹配商品",
|
||||
)
|
||||
return VerifyResult(
|
||||
verdict=VerifyVerdict.UNKNOWN,
|
||||
detail="订单列表反查未实现:见 docs/order-gateway.md §10 第 3 条",
|
||||
VerifyVerdict.UNKNOWN,
|
||||
detail="订单列表未能确认翻完任务创建时间之后的全部订单",
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user