fix: 合并近重叠地址入口候选 (#276)
This commit is contained in:
@@ -547,17 +547,70 @@ def _pdd_title_back_targets(root: ET.Element) -> list[Bounds]:
|
||||
]
|
||||
|
||||
|
||||
def _same_address_target(first: Bounds, second: Bounds) -> bool:
|
||||
"""判断两个边界是否是同一地址卡片的重复无障碍节点。"""
|
||||
|
||||
# 使用两倍中心坐标计算,避免浮点数;阈值等价于中心距离每轴 3 像素。
|
||||
if (
|
||||
abs((first[0] + first[2]) - (second[0] + second[2])) > 6
|
||||
or abs((first[1] + first[3]) - (second[1] + second[3])) > 6
|
||||
):
|
||||
return False
|
||||
intersection_width = max(
|
||||
0, min(first[2], second[2]) - max(first[0], second[0])
|
||||
)
|
||||
intersection_height = max(
|
||||
0, min(first[3], second[3]) - max(first[1], second[1])
|
||||
)
|
||||
intersection_area = intersection_width * intersection_height
|
||||
first_area = (first[2] - first[0]) * (first[3] - first[1])
|
||||
second_area = (second[2] - second[0]) * (second[3] - second[1])
|
||||
smaller_area = min(first_area, second_area)
|
||||
return smaller_area > 0 and intersection_area * 100 >= smaller_area * 98
|
||||
|
||||
|
||||
def _merge_address_targets(targets: list[Bounds]) -> list[Bounds]:
|
||||
"""严格合并近乎完全重叠的地址候选,并返回共同安全区域。"""
|
||||
|
||||
clusters: list[list[Bounds]] = []
|
||||
for target in sorted(set(targets)):
|
||||
cluster = next(
|
||||
(
|
||||
item
|
||||
for item in clusters
|
||||
if all(_same_address_target(target, other) for other in item)
|
||||
),
|
||||
None,
|
||||
)
|
||||
if cluster is None:
|
||||
clusters.append([target])
|
||||
else:
|
||||
cluster.append(target)
|
||||
|
||||
merged: list[Bounds] = []
|
||||
for cluster in clusters:
|
||||
intersection = (
|
||||
max(item[0] for item in cluster),
|
||||
max(item[1] for item in cluster),
|
||||
min(item[2] for item in cluster),
|
||||
min(item[3] for item in cluster),
|
||||
)
|
||||
if intersection[2] > intersection[0] and intersection[3] > intersection[1]:
|
||||
merged.append(intersection)
|
||||
return sorted(merged)
|
||||
|
||||
|
||||
def _address_entry_targets(root: ET.Element) -> list[Bounds]:
|
||||
"""通过脱敏手机号结构定位规格面板顶部的收货地址卡片。"""
|
||||
|
||||
parents = _parent_nodes(root)
|
||||
targets = {
|
||||
targets = [
|
||||
bounds
|
||||
for node in root.iter("node")
|
||||
if _MASKED_PHONE_PATTERN.search(_label(node))
|
||||
if (bounds := _nearest_clickable_bounds(node, parents)) is not None
|
||||
}
|
||||
return sorted(targets)
|
||||
]
|
||||
return _merge_address_targets(targets)
|
||||
|
||||
|
||||
def _shipping_address_editor(root: ET.Element) -> Optional[tuple[str, Bounds]]:
|
||||
|
||||
@@ -18,6 +18,7 @@ from src.performance_timing import TaskPerformanceTrace
|
||||
from src.pdd_u2_purchase_adapter import (
|
||||
U2PddLivePurchaseAdapter,
|
||||
U2PddPurchaseAdapter,
|
||||
_address_entry_targets,
|
||||
_final_submit_targets,
|
||||
_needs_color_region_restore,
|
||||
_page_kind,
|
||||
@@ -544,6 +545,29 @@ def address_confirmation_xml(address: str) -> str:
|
||||
</hierarchy>"""
|
||||
|
||||
|
||||
def duplicate_address_entry_xml(*, separated: bool = False) -> str:
|
||||
"""模拟同一卡片重复节点,或两个位置明显不同的真实候选。"""
|
||||
|
||||
second_top, second_bottom = ((620, 727) if separated else (366, 472))
|
||||
return f"""<hierarchy>
|
||||
<node package="com.xunmeng.pinduoduo" bounds="[0,0][1080,2376]">
|
||||
<node package="com.xunmeng.pinduoduo" clickable="true"
|
||||
enabled="true" visible-to-user="true"
|
||||
bounds="[0,366][1080,473]">
|
||||
<node package="com.xunmeng.pinduoduo"
|
||||
text="测试用户,000****0000" bounds="[412,366][993,384]"/>
|
||||
</node>
|
||||
<node package="com.xunmeng.pinduoduo" clickable="true"
|
||||
enabled="true" visible-to-user="true"
|
||||
bounds="[0,{second_top}][1080,{second_bottom}]">
|
||||
<node package="com.xunmeng.pinduoduo"
|
||||
text="测试用户,111****1111"
|
||||
bounds="[93,{second_top + 30}][960,{second_top + 76}]"/>
|
||||
</node>
|
||||
</node>
|
||||
</hierarchy>"""
|
||||
|
||||
|
||||
def clipped_address_confirmation_xml(address: str) -> str:
|
||||
"""首次地址卡片被顶部裁剪时的脱敏控件树。"""
|
||||
|
||||
@@ -1075,6 +1099,16 @@ class AmbiguousAfterModifyAddressDevice(AddressFlowDevice):
|
||||
|
||||
|
||||
class U2PddPurchaseAdapterTest(unittest.TestCase):
|
||||
def test_address_entry_merges_one_pixel_duplicate_nodes(self):
|
||||
root = ET.fromstring(duplicate_address_entry_xml())
|
||||
|
||||
self.assertEqual(_address_entry_targets(root), [(0, 366, 1080, 472)])
|
||||
|
||||
def test_address_entry_keeps_separate_cards_ambiguous(self):
|
||||
root = ET.fromstring(duplicate_address_entry_xml(separated=True))
|
||||
|
||||
self.assertEqual(len(_address_entry_targets(root)), 2)
|
||||
|
||||
def _adapter(self, device, calls):
|
||||
def select_color_fn(_device, _xml, target, **_kwargs):
|
||||
calls.append(("color", target))
|
||||
|
||||
Reference in New Issue
Block a user