fix: 合并近重叠地址入口候选 (#276)

This commit is contained in:
chengma
2026-08-18 17:42:47 +08:00
parent ff96f652c4
commit 7c56c29032
2 changed files with 90 additions and 3 deletions
+56 -3
View File
@@ -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))