Files
geMoldInsight/tests/test_experience_feedback_fingerprint.py
T

156 lines
5.7 KiB
Python
Raw Normal View History

"""D17 Human-in-Loop 闭环:compute_fingerprint 分桶边界值测试。
compute_fingerprint 是跨任务匹配的核心函数,纯函数,单独覆盖各分桶边界。
D17 验收:fingerprint 字典结构稳定 + 分桶边界值与 plan §5.1 一致。
"""
import pytest
from moldinsight.services.experience_feedback_service import compute_fingerprint
def _geo(volume: float = 50000.0, dims=(80, 60, 40), faces: int = 250, undercuts: int = 0):
"""构造测试用 geometry_data 字典。dims 单位 mm,volume 单位 mm³。"""
return {
"volume": volume,
"bounding_box": {
"dimensions": list(dims),
},
"topology_faces": faces,
"undercut_count": undercuts,
}
# ── bbox_aspect 分桶(按 sorted_dims mid/min 比)──
@pytest.mark.parametrize("dims,expected", [
# ratio = mid/min
((100, 100, 100), "compact"), # ratio=1.0 → compact(1.0 ≤ r < 1.5)
((100, 130, 100), "compact"), # ratio=1.0(去重后)
((60, 80, 100), "compact"), # ratio=1.0 → compact
((40, 80, 100), "slab"), # ratio=80/40=2.0 → slab(1.5 ≤ r < 3.0)
((20, 80, 100), "elongated"), # ratio=80/20=4.0 → elongated(3.0 ≤ r < 6.0)
((10, 80, 100), "long_bar"), # ratio=80/10=8 → long_bar(≥ 6.0)
((0, 0, 0), "compact"), # 零值默认
])
def test_bbox_aspect_bucket(dims, expected):
fp = compute_fingerprint(_geo(dims=dims), "ABS", False)
assert fp["bbox_aspect"] == expected, f"dims={dims} → expected {expected}, got {fp['bbox_aspect']}"
# ── volume_bucket 分桶(mm³ → cm³,buckets: xs<10 / s<100 / m<500 / l<2000 / xl≥2000)──
@pytest.mark.parametrize("volume_mm3,expected", [
(0, "xs"), # 0 cm³
(5000, "xs"), # 5 cm³
(9999, "xs"), # 边界 9.99 cm³ → xs
(10000, "s"), # 10 cm³ → s(10 ≤ v < 100)
(50000, "s"), # 50 cm³
(99999, "s"), # 边界 99.99 cm³
(100000, "m"), # 100 cm³ → m(100 ≤ v < 500)
(250000, "m"), # 250 cm³
(499999, "m"), # 边界 499.99 cm³
(500000, "l"), # 500 cm³ → l(500 ≤ v < 2000)
(1000000, "l"), # 1000 cm³
(1999999, "l"), # 边界 1999.99 cm³
(2000000, "xl"), # 2000 cm³ → xl(v ≥ 2000)
(10000000, "xl"), # 10000 cm³
])
def test_volume_bucket(volume_mm3, expected):
fp = compute_fingerprint(_geo(volume=volume_mm3), "ABS", False)
assert fp["volume_bucket"] == expected, f"volume={volume_mm3}mm³ → expected {expected}, got {fp['volume_bucket']}"
# ── face_bucket 分桶 ──
@pytest.mark.parametrize("faces,expected", [
(0, "simple"), # 0 面
(99, "simple"), # 边界 99
(100, "normal"), # 边界 100
(499, "normal"), # 边界 499
(500, "complex"), # 边界 500
(1999, "complex"), # 边界 1999
(2000, "dense"), # 边界 2000
(10000, "dense"),
])
def test_face_bucket(faces, expected):
fp = compute_fingerprint(_geo(faces=faces), "ABS", False)
assert fp["face_bucket"] == expected, f"faces={faces} → expected {expected}, got {fp['face_bucket']}"
# ── undercut_class 分桶 ──
@pytest.mark.parametrize("undercuts,expected", [
(0, "none"), # 0 → none
(1, "mild"), # 1 → mild
(3, "mild"), # 边界 3
(4, "moderate"), # 边界 4
(8, "moderate"), # 边界 8
(9, "heavy"), # 边界 9
(50, "heavy"),
])
def test_undercut_class(undercuts, expected):
fp = compute_fingerprint(_geo(undercuts=undercuts), "ABS", False)
assert fp["undercut_class"] == expected, f"undercuts={undercuts} → expected {expected}, got {fp['undercut_class']}"
# ── material_family 分桶 ──
@pytest.mark.parametrize("material,expected", [
("ABS", "abs"),
("ABS+PC", "abs"), # 子串匹配
("PP", "pp"),
("PA66", "pa"),
("AlSi10Mg", "foam"), # "al" + "si"
("AlSi12", "foam"),
("Aluminium Alloy", "other"), # 只有 al 无 si
("POM", "other"),
("", "other"),
])
def test_material_family(material, expected):
fp = compute_fingerprint(_geo(), material, False)
assert fp["material_family"] == expected, f"material={material} → expected {expected}, got {fp['material_family']}"
# ── is_foam 字段 ──
@pytest.mark.parametrize("is_foam,expected", [
(True, "true"),
(False, "false"),
])
def test_is_foam(is_foam, expected):
fp = compute_fingerprint(_geo(), "ABS", is_foam)
assert fp["is_foam"] == expected
# ── 字典键完整性 ──
def test_fingerprint_has_all_keys():
fp = compute_fingerprint(_geo(), "ABS", False)
expected_keys = {
"bbox_aspect", "volume_bucket", "face_bucket",
"undercut_class", "material_family", "is_foam",
}
assert set(fp.keys()) == expected_keys
# ── 空 geometry_data 容错 ──
def test_empty_geometry_data_returns_safe_defaults():
fp = compute_fingerprint(None, "ABS", False)
# 不应抛异常,所有键存在且为合法 bucket 值
assert fp["bbox_aspect"] == "compact"
assert fp["volume_bucket"] == "xs"
assert fp["face_bucket"] == "simple"
assert fp["undercut_class"] == "none"
assert fp["material_family"] == "abs"
assert fp["is_foam"] == "false"
def test_partial_geometry_data():
"""只有 bounding_box 没有 topology_faces,应走 fallback 0。"""
fp = compute_fingerprint(
{"bounding_box": {"dimensions": [100, 100, 100]}},
"ABS",
False,
)
assert fp["face_bucket"] == "simple" # 0 面 → simple