feat: 完整采集采购尺码候选 (#256)
This commit is contained in:
@@ -11,6 +11,7 @@ import re
|
||||
import time
|
||||
import xml.etree.ElementTree as ET
|
||||
from contextlib import nullcontext
|
||||
from datetime import datetime, timezone
|
||||
from decimal import Decimal, InvalidOperation, ROUND_HALF_UP
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable, Mapping, Optional
|
||||
@@ -24,6 +25,11 @@ from .pdd_device_service import (
|
||||
current_thread_device_service,
|
||||
)
|
||||
from .performance_timing import current_performance_trace
|
||||
from .pdd_collect_service import (
|
||||
PddCollectError,
|
||||
PddCollectService,
|
||||
SpecDimension,
|
||||
)
|
||||
from .pdd_page_classifier import (
|
||||
ACTION_NETWORK_ERROR,
|
||||
ACTION_READY,
|
||||
@@ -46,11 +52,15 @@ from .pdd_purchase_adapter import (
|
||||
PddLivePurchaseAdapter,
|
||||
PddPurchaseAdapter,
|
||||
PddPurchaseError,
|
||||
PddPurchaseSpecResolutionRequired,
|
||||
PurchaseSizeObservation,
|
||||
PurchaseSpecCandidateSnapshot,
|
||||
PurchasePageState,
|
||||
)
|
||||
from .util.get_size_panle_coord import get_size_panel_coord
|
||||
from .util.select_color_size import (
|
||||
color_selection_failure_reason,
|
||||
normalize_spec_text,
|
||||
select_color,
|
||||
select_size,
|
||||
size_selection_failure_reason,
|
||||
@@ -890,6 +900,10 @@ class U2PddPurchaseAdapter(PddPurchaseAdapter):
|
||||
panel_timeout: float = 10.0,
|
||||
select_color_fn: Callable[..., bool] = select_color,
|
||||
select_size_fn: Callable[..., bool] = select_size,
|
||||
size_candidate_collector: Optional[
|
||||
Callable[[Any], Optional[SpecDimension]]
|
||||
] = None,
|
||||
now: Callable[[], datetime] = lambda: datetime.now(timezone.utc),
|
||||
artifact_directory: Optional[Path] = None,
|
||||
) -> None:
|
||||
self._device_address = str(device_address or "").strip()
|
||||
@@ -901,6 +915,10 @@ class U2PddPurchaseAdapter(PddPurchaseAdapter):
|
||||
self._panel_timeout = panel_timeout
|
||||
self._select_color = select_color_fn
|
||||
self._select_size = select_size_fn
|
||||
self._size_candidate_collector = (
|
||||
size_candidate_collector or self._collect_size_candidates
|
||||
)
|
||||
self._now = now
|
||||
self._artifact_directory = artifact_directory
|
||||
self._last_xml: Optional[str] = None
|
||||
self._hierarchy_reads = 0
|
||||
@@ -1023,6 +1041,7 @@ class U2PddPurchaseAdapter(PddPurchaseAdapter):
|
||||
panel_xml = self._wait_for_confirmation_panel()
|
||||
|
||||
color = checked.get("color")
|
||||
selected_color_for_resolution = ""
|
||||
if color:
|
||||
panel_xml = self._restore_purchase_panel_color_region(panel_xml)
|
||||
trace = current_performance_trace()
|
||||
@@ -1065,6 +1084,7 @@ class U2PddPurchaseAdapter(PddPurchaseAdapter):
|
||||
step="purchase_select_options",
|
||||
diagnostics=diagnostics,
|
||||
)
|
||||
selected_color_for_resolution = color
|
||||
size = checked.get("size")
|
||||
if size:
|
||||
latest_xml = self._dump_hierarchy()
|
||||
@@ -1082,6 +1102,15 @@ class U2PddPurchaseAdapter(PddPurchaseAdapter):
|
||||
failure_reason = size_selection_failure_reason(
|
||||
failed_xml, size
|
||||
)
|
||||
if (
|
||||
failure_reason == "target_not_visible"
|
||||
and selected_color_for_resolution
|
||||
):
|
||||
self._raise_spec_resolution_required(
|
||||
failed_xml,
|
||||
selected_color_for_resolution,
|
||||
size,
|
||||
)
|
||||
messages = {
|
||||
"target_not_visible": f"没有找到目标尺码:{size}",
|
||||
"target_ambiguous": (
|
||||
@@ -1105,6 +1134,99 @@ class U2PddPurchaseAdapter(PddPurchaseAdapter):
|
||||
except Exception as exc:
|
||||
self._raise_device_or_page_error(exc, "purchase_select_options")
|
||||
|
||||
def _raise_spec_resolution_required(
|
||||
self,
|
||||
failed_xml: str,
|
||||
selected_color: str,
|
||||
target_size: str,
|
||||
) -> None:
|
||||
"""完整只读遍历后,仅为页面确实不存在的目标返回候选快照。"""
|
||||
|
||||
try:
|
||||
self._restore_purchase_panel_color_region(failed_xml)
|
||||
dimension = self._size_candidate_collector(self._require_device())
|
||||
except PddPurchaseError:
|
||||
raise
|
||||
except PddCollectError as exc:
|
||||
raise PddPurchaseError(
|
||||
"PURCHASE_SIZE_CANDIDATE_SCAN_INCOMPLETE",
|
||||
f"目标尺码未找到,且无法完整读取页面候选:{exc.message}",
|
||||
step="purchase_select_options",
|
||||
diagnostics={
|
||||
"selection_failure": "target_not_visible",
|
||||
"candidate_scan_error": exc.code,
|
||||
},
|
||||
) from exc
|
||||
except Exception as exc:
|
||||
self._raise_device_or_page_error(exc, "purchase_select_options")
|
||||
|
||||
if dimension is None or not dimension.values:
|
||||
raise PddPurchaseError(
|
||||
"PURCHASE_SIZE_CANDIDATE_SCAN_INCOMPLETE",
|
||||
"目标尺码未找到,且页面没有可确认完整的第二规格候选",
|
||||
step="purchase_select_options",
|
||||
diagnostics={"selection_failure": "target_not_visible"},
|
||||
)
|
||||
|
||||
available_values = tuple(
|
||||
item for item in dimension.values if item.available
|
||||
)
|
||||
if not available_values:
|
||||
raise PddPurchaseError(
|
||||
"PURCHASE_OPTIONS_MISMATCH",
|
||||
f"当前颜色没有可购买的尺码候选:{selected_color}",
|
||||
step="purchase_select_options",
|
||||
diagnostics={"selection_failure": "target_not_visible"},
|
||||
)
|
||||
|
||||
# 目标若在完整列表中精确或繁简等价出现,说明前面的点击/确认失败,
|
||||
# 不能把它伪装成需要远端解析的问题。
|
||||
normalized_target = normalize_spec_text(target_size)
|
||||
if any(
|
||||
item.text == target_size
|
||||
or normalize_spec_text(item.text) == normalized_target
|
||||
for item in available_values
|
||||
):
|
||||
raise PddPurchaseError(
|
||||
"PURCHASE_OPTIONS_MISMATCH",
|
||||
f"页面存在目标尺码,但没有可靠确认选中:{target_size}",
|
||||
step="purchase_select_options",
|
||||
diagnostics={"selection_failure": "selection_unconfirmed"},
|
||||
)
|
||||
|
||||
observed_at = (
|
||||
self._now()
|
||||
.astimezone(timezone.utc)
|
||||
.isoformat()
|
||||
.replace("+00:00", "Z")
|
||||
)
|
||||
snapshot = PurchaseSpecCandidateSnapshot.build(
|
||||
goods_id=self._goods_id,
|
||||
selected_color=selected_color,
|
||||
target_size=target_size,
|
||||
dimension_name=dimension.name,
|
||||
observations=tuple(
|
||||
PurchaseSizeObservation(item.text, item.available)
|
||||
for item in dimension.values
|
||||
),
|
||||
observed_at=observed_at,
|
||||
)
|
||||
raise PddPurchaseSpecResolutionRequired(snapshot)
|
||||
|
||||
def _collect_size_candidates(self, device: Any) -> Optional[SpecDimension]:
|
||||
"""使用采集模块同一套第二规格遍历,不建立新连接也不保存 XML。"""
|
||||
|
||||
service = PddCollectService(
|
||||
self._device_service,
|
||||
self._device_address,
|
||||
"purchase-runtime",
|
||||
sleeper=self._sleep,
|
||||
monotonic=self._monotonic,
|
||||
cancelled=self._cancelled,
|
||||
artifact_directory=None,
|
||||
)
|
||||
return service.collect_second_dimension_candidates(device)
|
||||
|
||||
def set_quantity(self, quantity: int) -> None:
|
||||
device = self._require_device()
|
||||
if isinstance(quantity, bool) or not isinstance(quantity, int) or quantity <= 0:
|
||||
|
||||
Reference in New Issue
Block a user