fix: AI 返回后先选尺码再核对组合 (#267)
This commit is contained in:
@@ -1303,19 +1303,6 @@ class U2PddPurchaseAdapter(PddPurchaseAdapter):
|
||||
"observed_page_kind": current_page_kind,
|
||||
},
|
||||
)
|
||||
# 完整遍历第二规格后,面板可能停在尺码列表末端,颜色节点已经
|
||||
# 滑出当前控件树。必须先回到颜色区域,再用最新控件树确认原颜色;
|
||||
# 否则“当前看不见”会被误报成“颜色发生变化”。
|
||||
current_xml = self._restore_purchase_panel_color_region(current_xml)
|
||||
root = _parse_xml(current_xml)
|
||||
if not _selected(root, expected_snapshot.selected_color):
|
||||
raise PddPurchaseError(
|
||||
"PURCHASE_SPEC_CONTEXT_CHANGED",
|
||||
"等待规格解析期间已选颜色发生变化",
|
||||
step="purchase_resolve_options",
|
||||
diagnostics={"context_check": "selected_color"},
|
||||
)
|
||||
|
||||
try:
|
||||
dimension = self._size_candidate_collector(self._require_device())
|
||||
except PddCollectError as exc:
|
||||
@@ -1355,9 +1342,10 @@ class U2PddPurchaseAdapter(PddPurchaseAdapter):
|
||||
step="purchase_resolve_options",
|
||||
)
|
||||
|
||||
latest_xml = self._restore_purchase_panel_color_region(
|
||||
self._dump_hierarchy()
|
||||
)
|
||||
# 候选复算后仍停留在尺码上下文时,直接选择 Admin 返回的页面
|
||||
# 原文。颜色节点当前不可见不代表颜色已变化;完整组合在点击后
|
||||
# 使用最新“已选”摘要核对,摘要不足时才回到颜色区域兜底。
|
||||
latest_xml = self._dump_hierarchy()
|
||||
if not self._select_size(
|
||||
self._require_device(), latest_xml, candidate.raw_text, action_delay=0.2
|
||||
):
|
||||
@@ -1367,12 +1355,35 @@ class U2PddPurchaseAdapter(PddPurchaseAdapter):
|
||||
step="purchase_resolve_options",
|
||||
)
|
||||
selected_xml = self._dump_hierarchy()
|
||||
if not _selected(_parse_xml(selected_xml), candidate.raw_text):
|
||||
selected_root = _parse_xml(selected_xml)
|
||||
selected_page_kind = _page_kind(selected_root, "")
|
||||
if selected_page_kind != "order_confirmation":
|
||||
raise PddPurchaseError(
|
||||
"PURCHASE_SPEC_CONTEXT_CHANGED",
|
||||
"选择解析尺码后页面已离开规格确认页",
|
||||
step="purchase_resolve_options",
|
||||
diagnostics={
|
||||
"context_check": "page_kind_after_size",
|
||||
"expected_page_kind": "order_confirmation",
|
||||
"observed_page_kind": selected_page_kind,
|
||||
},
|
||||
)
|
||||
if not _selected(selected_root, candidate.raw_text):
|
||||
raise PddPurchaseError(
|
||||
"PURCHASE_RESOLVED_SIZE_NOT_SELECTED",
|
||||
"点击解析尺码后页面没有确认选中",
|
||||
step="purchase_resolve_options",
|
||||
)
|
||||
if not _selected(selected_root, expected_snapshot.selected_color):
|
||||
color_xml = self._restore_purchase_panel_color_region(selected_xml)
|
||||
color_root = _parse_xml(color_xml)
|
||||
if not _selected(color_root, expected_snapshot.selected_color):
|
||||
raise PddPurchaseError(
|
||||
"PURCHASE_SPEC_CONTEXT_CHANGED",
|
||||
"选择解析尺码后无法确认原颜色仍处于选中状态",
|
||||
step="purchase_resolve_options",
|
||||
diagnostics={"context_check": "selected_color"},
|
||||
)
|
||||
self._requested_options["size"] = candidate.raw_text
|
||||
|
||||
def set_quantity(self, quantity: int) -> None:
|
||||
|
||||
@@ -266,12 +266,14 @@ class VerticallyScrolledColorPanelDevice(FakeDevice):
|
||||
starts_at_top: bool,
|
||||
restores_on_swipe: bool = True,
|
||||
selected_color: str = "",
|
||||
show_selected_summary: bool = False,
|
||||
):
|
||||
super().__init__()
|
||||
self.at_color_top = starts_at_top
|
||||
self.restores_on_swipe = restores_on_swipe
|
||||
self.selected_color = selected_color
|
||||
self.selected_size = ""
|
||||
self.show_selected_summary = show_selected_summary
|
||||
self.swipes = []
|
||||
|
||||
def dump_hierarchy(self):
|
||||
@@ -293,6 +295,18 @@ class VerticallyScrolledColorPanelDevice(FakeDevice):
|
||||
'<node text="均码" clickable="true"',
|
||||
'<node text="均码" selected="true" clickable="true"',
|
||||
)
|
||||
if (
|
||||
not self.at_color_top
|
||||
and self.show_selected_summary
|
||||
and self.selected_color
|
||||
):
|
||||
summary = self.selected_color
|
||||
if self.selected_size:
|
||||
summary = f"{summary} {self.selected_size}"
|
||||
xml_data = xml_data.replace(
|
||||
'text="请选择: 颜色分类"',
|
||||
f'text="已选: {summary}"',
|
||||
)
|
||||
return xml_data
|
||||
return super().dump_hierarchy()
|
||||
|
||||
@@ -1117,7 +1131,48 @@ class U2PddPurchaseAdapterTest(unittest.TestCase):
|
||||
)
|
||||
adapter.close()
|
||||
|
||||
def test_apply_resolved_size_restores_hidden_selected_color_before_recheck(self):
|
||||
def test_apply_resolved_size_uses_complete_summary_without_color_restore(self):
|
||||
clock = FakeClock()
|
||||
device = VerticallyScrolledColorPanelDevice(
|
||||
starts_at_top=False,
|
||||
selected_color="黑色",
|
||||
show_selected_summary=True,
|
||||
)
|
||||
selected_sizes = []
|
||||
snapshot = PurchaseSpecCandidateSnapshot.build(
|
||||
goods_id="753136429979",
|
||||
selected_color="黑色",
|
||||
target_size="50公斤",
|
||||
dimension_name="尺码",
|
||||
observations=(PurchaseSizeObservation("均码", True),),
|
||||
observed_at="2026-08-18T04:00:00Z",
|
||||
)
|
||||
|
||||
def select_size(_device, _xml, target, **_kwargs):
|
||||
selected_sizes.append(target)
|
||||
device.selected_size = target
|
||||
return True
|
||||
|
||||
adapter = U2PddPurchaseAdapter(
|
||||
"USB-001",
|
||||
device_service=PddDeviceService(connector=lambda _serial: device),
|
||||
sleeper=clock.sleep,
|
||||
monotonic=clock.monotonic,
|
||||
select_size_fn=select_size,
|
||||
size_candidate_collector=lambda _device: SpecDimension(
|
||||
"size", "尺码", (DimensionValue("均码", True),)
|
||||
),
|
||||
)
|
||||
adapter.open_goods(GOODS_URL)
|
||||
device.mode = "panel"
|
||||
|
||||
adapter.apply_resolved_size(snapshot, snapshot.candidates[0])
|
||||
|
||||
self.assertEqual(device.swipes, [])
|
||||
self.assertEqual(selected_sizes, ["均码"])
|
||||
adapter.close()
|
||||
|
||||
def test_apply_resolved_size_restores_color_when_summary_is_incomplete(self):
|
||||
clock = FakeClock()
|
||||
device = VerticallyScrolledColorPanelDevice(
|
||||
starts_at_top=False,
|
||||
@@ -1158,7 +1213,7 @@ class U2PddPurchaseAdapterTest(unittest.TestCase):
|
||||
self.assertEqual(selected_sizes, ["均码"])
|
||||
adapter.close()
|
||||
|
||||
def test_apply_resolved_size_stops_when_hidden_color_cannot_be_restored(self):
|
||||
def test_apply_resolved_size_stops_when_color_fallback_cannot_be_restored(self):
|
||||
clock = FakeClock()
|
||||
device = VerticallyScrolledColorPanelDevice(
|
||||
starts_at_top=False,
|
||||
@@ -1175,13 +1230,25 @@ class U2PddPurchaseAdapterTest(unittest.TestCase):
|
||||
observations=(PurchaseSizeObservation("均码", True),),
|
||||
observed_at="2026-08-18T04:00:00Z",
|
||||
)
|
||||
|
||||
def select_size(_device, _xml, target, **_kwargs):
|
||||
selected_sizes.append(target)
|
||||
device.selected_size = target
|
||||
return True
|
||||
|
||||
def collect_candidates(_device):
|
||||
candidate_scans.append(True)
|
||||
return SpecDimension(
|
||||
"size", "尺码", (DimensionValue("均码", True),)
|
||||
)
|
||||
|
||||
adapter = U2PddPurchaseAdapter(
|
||||
"USB-001",
|
||||
device_service=PddDeviceService(connector=lambda _serial: device),
|
||||
sleeper=clock.sleep,
|
||||
monotonic=clock.monotonic,
|
||||
select_size_fn=lambda *_args, **_kwargs: selected_sizes.append(True),
|
||||
size_candidate_collector=lambda _device: candidate_scans.append(True),
|
||||
select_size_fn=select_size,
|
||||
size_candidate_collector=collect_candidates,
|
||||
)
|
||||
adapter.open_goods(GOODS_URL)
|
||||
device.mode = "panel"
|
||||
@@ -1194,8 +1261,49 @@ class U2PddPurchaseAdapterTest(unittest.TestCase):
|
||||
"PURCHASE_PANEL_COLOR_REGION_NOT_FOUND",
|
||||
)
|
||||
self.assertEqual(len(device.swipes), 2)
|
||||
self.assertEqual(candidate_scans, [])
|
||||
self.assertEqual(selected_sizes, [])
|
||||
self.assertEqual(candidate_scans, [True])
|
||||
self.assertEqual(selected_sizes, ["均码"])
|
||||
adapter.close()
|
||||
|
||||
def test_apply_resolved_size_rechecks_panel_after_selecting_returned_size(self):
|
||||
device = FakeDevice()
|
||||
resolved_size = "均码【80-120斤】"
|
||||
snapshot = PurchaseSpecCandidateSnapshot.build(
|
||||
goods_id="753136429979",
|
||||
selected_color="黑色",
|
||||
target_size="均码【40-60公斤】",
|
||||
dimension_name="尺码",
|
||||
observations=(PurchaseSizeObservation(resolved_size, True),),
|
||||
observed_at="2026-08-18T04:00:00Z",
|
||||
)
|
||||
|
||||
def leave_panel_after_size(*_args, **_kwargs):
|
||||
device.special_xml = home_xml()
|
||||
return True
|
||||
|
||||
adapter = U2PddPurchaseAdapter(
|
||||
"USB-001",
|
||||
device_service=PddDeviceService(connector=lambda _serial: device),
|
||||
sleeper=lambda _seconds: None,
|
||||
select_size_fn=leave_panel_after_size,
|
||||
size_candidate_collector=lambda _device: SpecDimension(
|
||||
"size", "尺码", (DimensionValue(resolved_size, True),)
|
||||
),
|
||||
)
|
||||
adapter.open_goods(GOODS_URL)
|
||||
device.mode = "special"
|
||||
device.special_xml = panel_xml().replace(
|
||||
"3XL【140-165斤】", resolved_size
|
||||
)
|
||||
|
||||
with self.assertRaises(PddPurchaseError) as raised:
|
||||
adapter.apply_resolved_size(snapshot, snapshot.candidates[0])
|
||||
|
||||
self.assertEqual(raised.exception.code, "PURCHASE_SPEC_CONTEXT_CHANGED")
|
||||
self.assertEqual(
|
||||
raised.exception.diagnostics["context_check"],
|
||||
"page_kind_after_size",
|
||||
)
|
||||
adapter.close()
|
||||
|
||||
def test_apply_resolved_size_distinguishes_page_and_color_context_changes(self):
|
||||
@@ -1217,7 +1325,7 @@ class U2PddPurchaseAdapterTest(unittest.TestCase):
|
||||
.replace("已选: 黑色", "已选: 白色")
|
||||
.replace("黑色", "缥缈仙姬绾【白色】")
|
||||
.replace("3XL【140-165斤】", resolved_size),
|
||||
"已选颜色发生变化",
|
||||
"无法确认原颜色仍处于选中状态",
|
||||
),
|
||||
)
|
||||
for context_check, current_xml, expected_message in cases:
|
||||
|
||||
@@ -124,8 +124,9 @@ Client 不保存或读取 `purchase.live_*` 手工授权设置。`purchase_mode`
|
||||
逐字副本时才继续。响应返回后重新完整遍历真机候选并复算哈希,页面、颜色或候选
|
||||
有任何变化都在可逆阶段停止。
|
||||
- 完整遍历第二规格可能把已选颜色滑出当前控件树。Admin 返回解析结果后,Client
|
||||
必须先有界恢复到颜色区域,再读取最新控件树确认原颜色仍明确选中;无法恢复与
|
||||
明确换色分别安全停止,不能把暂时不可见当作颜色已经变化,也不能跳过颜色复核。
|
||||
先在尺码上下文复算候选并选择返回尺码,再从最新“已选”摘要一次核对颜色和尺码;
|
||||
摘要不能完整确认颜色时才有界恢复颜色区域兜底。无法恢复或无法确认都安全停止,
|
||||
不能把暂时不可见当作颜色已经变化,也不能用历史记录代替当前页面证据。
|
||||
- 解析记录不能覆盖任务原始规格。成功结果同时保留原始规格、实际采用规格和
|
||||
`resolution_id`;解析失败不自动重新采购,已有不可逆标记时永远不调用规格解析。
|
||||
- 设置采购数量时先读取当前值;数量相同不聚焦输入框,小差值优先使用加减按钮。
|
||||
|
||||
Reference in New Issue
Block a user