feat(tree/repair): 检测器扩展 — visible_entities/ongoing_actions/spatial_layout 为空也触发修复
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
"""修复检测器单元测试。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from app.tree.index import (
|
||||
IndexMeta,
|
||||
L1Card,
|
||||
L1Node,
|
||||
L2Card,
|
||||
L2Node,
|
||||
L3Card,
|
||||
L3Node,
|
||||
TreeIndex,
|
||||
)
|
||||
from app.tree.repair.detector import detect_issues
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def _card_l3(summary: str = "正常描述") -> L3Card:
|
||||
return L3Card(summary, ["实体"], ["动作"], [], "居中", {})
|
||||
|
||||
|
||||
def _card_l2() -> L2Card:
|
||||
return L2Card("事件", [], [], [], [], "", None)
|
||||
|
||||
|
||||
def _card_l1() -> L1Card:
|
||||
return L1Card("场景", "", [], [], [], [], "")
|
||||
|
||||
|
||||
class TestDetectIssues:
|
||||
def test_healthy_tree_no_issues(self) -> None:
|
||||
l3 = L3Node(id="l1_0_l2_0_l3_0", card=_card_l3(), timestamp=1.0)
|
||||
l2 = L2Node(
|
||||
id="l1_0_l2_0",
|
||||
card=_card_l2(),
|
||||
time_range=(0.0, 10.0),
|
||||
children=[l3],
|
||||
)
|
||||
l1 = L1Node(
|
||||
id="l1_0",
|
||||
card=_card_l1(),
|
||||
time_range=(0.0, 10.0),
|
||||
children=[l2],
|
||||
)
|
||||
index = TreeIndex(metadata=IndexMeta("/t.mp4", "video"), roots=[l1])
|
||||
assert detect_issues(index) == []
|
||||
|
||||
def test_empty_frame_summary(self) -> None:
|
||||
l3 = L3Node(id="l1_0_l2_0_l3_0", card=_card_l3(""), timestamp=1.0)
|
||||
l2 = L2Node(
|
||||
id="l1_0_l2_0",
|
||||
card=_card_l2(),
|
||||
time_range=(0.0, 10.0),
|
||||
children=[l3],
|
||||
)
|
||||
l1 = L1Node(
|
||||
id="l1_0",
|
||||
card=_card_l1(),
|
||||
time_range=(0.0, 10.0),
|
||||
children=[l2],
|
||||
)
|
||||
index = TreeIndex(metadata=IndexMeta("/t.mp4", "video"), roots=[l1])
|
||||
issues = detect_issues(index)
|
||||
assert any(i.issue_type == "empty_field" and i.node_id == "l1_0_l2_0_l3_0" for i in issues)
|
||||
|
||||
def test_missing_frame_file(self, tmp_path: Path) -> None:
|
||||
l3 = L3Node(
|
||||
id="l1_0_l2_0_l3_0",
|
||||
card=_card_l3(),
|
||||
timestamp=1.0,
|
||||
frame_path="frames/missing.jpg",
|
||||
)
|
||||
l2 = L2Node(
|
||||
id="l1_0_l2_0",
|
||||
card=_card_l2(),
|
||||
time_range=(0.0, 10.0),
|
||||
children=[l3],
|
||||
)
|
||||
l1 = L1Node(
|
||||
id="l1_0",
|
||||
card=_card_l1(),
|
||||
time_range=(0.0, 10.0),
|
||||
children=[l2],
|
||||
)
|
||||
index = TreeIndex(metadata=IndexMeta("/t.mp4", "video"), roots=[l1])
|
||||
issues = detect_issues(index, frames_dir=tmp_path)
|
||||
assert any(i.issue_type == "missing_frame" for i in issues)
|
||||
|
||||
def test_l2_no_children(self) -> None:
|
||||
l2 = L2Node(
|
||||
id="l1_0_l2_0",
|
||||
card=_card_l2(),
|
||||
time_range=(0.0, 10.0),
|
||||
children=[],
|
||||
)
|
||||
l1 = L1Node(
|
||||
id="l1_0",
|
||||
card=_card_l1(),
|
||||
time_range=(0.0, 10.0),
|
||||
children=[l2],
|
||||
)
|
||||
index = TreeIndex(metadata=IndexMeta("/t.mp4", "video"), roots=[l1])
|
||||
issues = detect_issues(index)
|
||||
assert any(i.issue_type == "no_children" and i.level == 2 for i in issues)
|
||||
|
||||
def test_l1_no_children(self) -> None:
|
||||
l1 = L1Node(
|
||||
id="l1_0",
|
||||
card=_card_l1(),
|
||||
time_range=(0.0, 10.0),
|
||||
children=[],
|
||||
)
|
||||
index = TreeIndex(metadata=IndexMeta("/t.mp4", "video"), roots=[l1])
|
||||
issues = detect_issues(index)
|
||||
assert any(i.issue_type == "no_children" and i.level == 1 for i in issues)
|
||||
|
||||
def test_empty_visible_entities(self) -> None:
|
||||
"""visible_entities 为空也触发 empty_field。"""
|
||||
card = L3Card("正常描述", [], ["动作"], [], "居中", {})
|
||||
l3 = L3Node(id="l1_0_l2_0_l3_0", card=card, timestamp=1.0)
|
||||
l2 = L2Node(id="l1_0_l2_0", card=_card_l2(), time_range=(0.0, 10.0), children=[l3])
|
||||
l1 = L1Node(id="l1_0", card=_card_l1(), time_range=(0.0, 10.0), children=[l2])
|
||||
index = TreeIndex(metadata=IndexMeta("/t.mp4", "video"), roots=[l1])
|
||||
issues = detect_issues(index)
|
||||
assert any(i.issue_type == "empty_field" and "visible_entities" in i.details for i in issues)
|
||||
|
||||
def test_empty_ongoing_actions(self) -> None:
|
||||
"""ongoing_actions 为空也触发 empty_field。"""
|
||||
card = L3Card("正常描述", ["实体"], [], [], "居中", {})
|
||||
l3 = L3Node(id="l1_0_l2_0_l3_0", card=card, timestamp=1.0)
|
||||
l2 = L2Node(id="l1_0_l2_0", card=_card_l2(), time_range=(0.0, 10.0), children=[l3])
|
||||
l1 = L1Node(id="l1_0", card=_card_l1(), time_range=(0.0, 10.0), children=[l2])
|
||||
index = TreeIndex(metadata=IndexMeta("/t.mp4", "video"), roots=[l1])
|
||||
issues = detect_issues(index)
|
||||
assert any(i.issue_type == "empty_field" and "ongoing_actions" in i.details for i in issues)
|
||||
|
||||
def test_empty_spatial_layout(self) -> None:
|
||||
"""spatial_layout 为空也触发 empty_field。"""
|
||||
card = L3Card("正常描述", ["实体"], ["动作"], [], "", {})
|
||||
l3 = L3Node(id="l1_0_l2_0_l3_0", card=card, timestamp=1.0)
|
||||
l2 = L2Node(id="l1_0_l2_0", card=_card_l2(), time_range=(0.0, 10.0), children=[l3])
|
||||
l1 = L1Node(id="l1_0", card=_card_l1(), time_range=(0.0, 10.0), children=[l2])
|
||||
index = TreeIndex(metadata=IndexMeta("/t.mp4", "video"), roots=[l1])
|
||||
issues = detect_issues(index)
|
||||
assert any(i.issue_type == "empty_field" and "spatial_layout" in i.details for i in issues)
|
||||
|
||||
def test_multiple_empty_fields_single_issue(self) -> None:
|
||||
"""多个字段同时为空只产生一个 issue,details 列出所有空字段。"""
|
||||
card = L3Card("", [], [], [], "", {})
|
||||
l3 = L3Node(id="l1_0_l2_0_l3_0", card=card, timestamp=1.0)
|
||||
l2 = L2Node(id="l1_0_l2_0", card=_card_l2(), time_range=(0.0, 10.0), children=[l3])
|
||||
l1 = L1Node(id="l1_0", card=_card_l1(), time_range=(0.0, 10.0), children=[l2])
|
||||
index = TreeIndex(metadata=IndexMeta("/t.mp4", "video"), roots=[l1])
|
||||
issues = [i for i in detect_issues(index) if i.issue_type == "empty_field"]
|
||||
assert len(issues) == 1
|
||||
assert "frame_summary" in issues[0].details
|
||||
assert "visible_entities" in issues[0].details
|
||||
|
||||
def test_time_gap(self) -> None:
|
||||
l3_a = L3Node(id="l1_0_l2_0_l3_0", card=_card_l3(), timestamp=1.0)
|
||||
l3_b = L3Node(id="l1_0_l2_1_l3_0", card=_card_l3(), timestamp=20.0)
|
||||
l2_a = L2Node(
|
||||
id="l1_0_l2_0",
|
||||
card=_card_l2(),
|
||||
time_range=(0.0, 5.0),
|
||||
children=[l3_a],
|
||||
)
|
||||
l2_b = L2Node(
|
||||
id="l1_0_l2_1",
|
||||
card=_card_l2(),
|
||||
time_range=(15.0, 25.0),
|
||||
children=[l3_b],
|
||||
)
|
||||
l1 = L1Node(
|
||||
id="l1_0",
|
||||
card=_card_l1(),
|
||||
time_range=(0.0, 25.0),
|
||||
children=[l2_a, l2_b],
|
||||
)
|
||||
index = TreeIndex(metadata=IndexMeta("/t.mp4", "video"), roots=[l1])
|
||||
issues = detect_issues(index)
|
||||
assert any(i.issue_type == "time_gap" for i in issues)
|
||||
Reference in New Issue
Block a user