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]:
|
def _address_entry_targets(root: ET.Element) -> list[Bounds]:
|
||||||
"""通过脱敏手机号结构定位规格面板顶部的收货地址卡片。"""
|
"""通过脱敏手机号结构定位规格面板顶部的收货地址卡片。"""
|
||||||
|
|
||||||
parents = _parent_nodes(root)
|
parents = _parent_nodes(root)
|
||||||
targets = {
|
targets = [
|
||||||
bounds
|
bounds
|
||||||
for node in root.iter("node")
|
for node in root.iter("node")
|
||||||
if _MASKED_PHONE_PATTERN.search(_label(node))
|
if _MASKED_PHONE_PATTERN.search(_label(node))
|
||||||
if (bounds := _nearest_clickable_bounds(node, parents)) is not None
|
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]]:
|
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 (
|
from src.pdd_u2_purchase_adapter import (
|
||||||
U2PddLivePurchaseAdapter,
|
U2PddLivePurchaseAdapter,
|
||||||
U2PddPurchaseAdapter,
|
U2PddPurchaseAdapter,
|
||||||
|
_address_entry_targets,
|
||||||
_final_submit_targets,
|
_final_submit_targets,
|
||||||
_needs_color_region_restore,
|
_needs_color_region_restore,
|
||||||
_page_kind,
|
_page_kind,
|
||||||
@@ -544,6 +545,29 @@ def address_confirmation_xml(address: str) -> str:
|
|||||||
</hierarchy>"""
|
</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:
|
def clipped_address_confirmation_xml(address: str) -> str:
|
||||||
"""首次地址卡片被顶部裁剪时的脱敏控件树。"""
|
"""首次地址卡片被顶部裁剪时的脱敏控件树。"""
|
||||||
|
|
||||||
@@ -1075,6 +1099,16 @@ class AmbiguousAfterModifyAddressDevice(AddressFlowDevice):
|
|||||||
|
|
||||||
|
|
||||||
class U2PddPurchaseAdapterTest(unittest.TestCase):
|
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 _adapter(self, device, calls):
|
||||||
def select_color_fn(_device, _xml, target, **_kwargs):
|
def select_color_fn(_device, _xml, target, **_kwargs):
|
||||||
calls.append(("color", target))
|
calls.append(("color", target))
|
||||||
|
|||||||
Reference in New Issue
Block a user