@@ -169,6 +169,27 @@ def _normalize_rule(rule: dict) -> dict:
|
|||||||
"value": "active"
|
"value": "active"
|
||||||
}]
|
}]
|
||||||
|
|
||||||
|
# Ensure table/text sources have a section field (defensive against LLM omission)
|
||||||
|
sources = rule.get("sources", [])
|
||||||
|
if sources:
|
||||||
|
# try to infer a default section from sibling sources or the rule path
|
||||||
|
default_section = ""
|
||||||
|
for s in sources:
|
||||||
|
sec = s.get("section", "")
|
||||||
|
if sec and sec.strip():
|
||||||
|
default_section = sec.strip()
|
||||||
|
break
|
||||||
|
if not default_section:
|
||||||
|
path = rule.get("path", "")
|
||||||
|
if path:
|
||||||
|
default_section = path.split(" > ")[0] if " > " in path else path
|
||||||
|
|
||||||
|
for src in sources:
|
||||||
|
stype = src.get("type", "")
|
||||||
|
if stype in ("table", "text"):
|
||||||
|
if not src.get("section"):
|
||||||
|
src["section"] = default_section
|
||||||
|
|
||||||
return rule
|
return rule
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -465,3 +465,49 @@ class TestNormalizeRule:
|
|||||||
normalized = _normalize_rule(rule)
|
normalized = _normalize_rule(rule)
|
||||||
assert normalized["trigger"]["operator"] == "AND"
|
assert normalized["trigger"]["operator"] == "AND"
|
||||||
assert normalized["trigger"]["conditions"][0]["operator"] == ">="
|
assert normalized["trigger"]["conditions"][0]["operator"] == ">="
|
||||||
|
|
||||||
|
def test_normalize_source_missing_section_from_sibling(self):
|
||||||
|
"""Table/text sources without section get it from sibling sources."""
|
||||||
|
rule = {
|
||||||
|
"trigger": {"conditions": [{"signal": "x", "operator": "==", "value": "1"}]},
|
||||||
|
"sources": [
|
||||||
|
{"type": "table", "section": "3.1.1 系统限制", "row": 1},
|
||||||
|
{"type": "text", "text_snippet": "missing section"},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
normalized = _normalize_rule(rule)
|
||||||
|
assert normalized["sources"][1]["section"] == "3.1.1 系统限制"
|
||||||
|
|
||||||
|
def test_normalize_source_missing_section_from_path(self):
|
||||||
|
"""Table/text sources without section and no sibling fall back to rule path."""
|
||||||
|
rule = {
|
||||||
|
"trigger": {"conditions": [{"signal": "x", "operator": "==", "value": "1"}]},
|
||||||
|
"path": "4.2 关闭流程 > decision_speed > action_disable",
|
||||||
|
"sources": [
|
||||||
|
{"type": "table", "row": 3, "text_snippet": "no section anywhere"},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
normalized = _normalize_rule(rule)
|
||||||
|
assert normalized["sources"][0]["section"] == "4.2 关闭流程"
|
||||||
|
|
||||||
|
def test_normalize_source_keeps_existing_section(self):
|
||||||
|
"""Sources that already have section are not modified."""
|
||||||
|
rule = {
|
||||||
|
"trigger": {"conditions": [{"signal": "x", "operator": "==", "value": "1"}]},
|
||||||
|
"sources": [
|
||||||
|
{"type": "table", "section": "1.0 概述", "row": 1},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
normalized = _normalize_rule(rule)
|
||||||
|
assert normalized["sources"][0]["section"] == "1.0 概述"
|
||||||
|
|
||||||
|
def test_normalize_source_skips_logic_tree(self):
|
||||||
|
"""Logic tree sources are not touched (don't need section)."""
|
||||||
|
rule = {
|
||||||
|
"trigger": {"conditions": [{"signal": "x", "operator": "==", "value": "1"}]},
|
||||||
|
"sources": [
|
||||||
|
{"type": "logic_tree", "image_id": "img1", "node_ids": ["n1"]},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
normalized = _normalize_rule(rule)
|
||||||
|
assert "section" not in normalized["sources"][0]
|
||||||
|
|||||||
Reference in New Issue
Block a user