Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a24408521c | |||
| c091b6c256 | |||
| cbafd30ec7 | |||
| f84908aa36 |
@@ -172,10 +172,9 @@ def _normalize_rule(rule: dict) -> dict:
|
|||||||
# Ensure table/text sources have a section field (defensive against LLM omission)
|
# Ensure table/text sources have a section field (defensive against LLM omission)
|
||||||
# Also normalize invalid source types (LLM hallucinations like function_unit_description)
|
# Also normalize invalid source types (LLM hallucinations like function_unit_description)
|
||||||
sources = rule.get("sources", [])
|
sources = rule.get("sources", [])
|
||||||
if sources:
|
|
||||||
valid_types = {"table", "text", "logic_tree"}
|
valid_types = {"table", "text", "logic_tree"}
|
||||||
|
|
||||||
# try to infer a default section from sibling sources or the rule path
|
# try to infer a default section from the rule path
|
||||||
default_section = ""
|
default_section = ""
|
||||||
for s in sources:
|
for s in sources:
|
||||||
sec = s.get("section", "")
|
sec = s.get("section", "")
|
||||||
@@ -187,15 +186,22 @@ def _normalize_rule(rule: dict) -> dict:
|
|||||||
if path:
|
if path:
|
||||||
default_section = path.split(" > ")[0] if " > " in path else path
|
default_section = path.split(" > ")[0] if " > " in path else path
|
||||||
|
|
||||||
|
if sources:
|
||||||
for src in sources:
|
for src in sources:
|
||||||
stype = src.get("type", "")
|
stype = src.get("type", "")
|
||||||
# Normalize invalid source types to "text"
|
|
||||||
if stype and stype not in valid_types:
|
if stype and stype not in valid_types:
|
||||||
src["type"] = "text"
|
src["type"] = "text"
|
||||||
stype = "text"
|
stype = "text"
|
||||||
if stype in ("table", "text"):
|
if stype in ("table", "text"):
|
||||||
if not src.get("section"):
|
if not src.get("section"):
|
||||||
src["section"] = default_section
|
src["section"] = default_section
|
||||||
|
else:
|
||||||
|
# Empty sources list — add a minimal text source (defensive against schema failure)
|
||||||
|
src = {"type": "text", "text_snippet": "inferred from rule context"}
|
||||||
|
if default_section:
|
||||||
|
src["section"] = default_section
|
||||||
|
sources.append(src)
|
||||||
|
rule["sources"] = sources
|
||||||
|
|
||||||
return rule
|
return rule
|
||||||
|
|
||||||
|
|||||||
@@ -526,3 +526,15 @@ class TestNormalizeRule:
|
|||||||
assert normalized["sources"][0]["type"] == "text"
|
assert normalized["sources"][0]["type"] == "text"
|
||||||
assert normalized["sources"][1]["type"] == "text"
|
assert normalized["sources"][1]["type"] == "text"
|
||||||
assert normalized["sources"][0]["section"] == "3.1 功能"
|
assert normalized["sources"][0]["section"] == "3.1 功能"
|
||||||
|
|
||||||
|
def test_normalize_empty_sources(self):
|
||||||
|
"""Rules with empty sources get a minimal text source (defensive)."""
|
||||||
|
rule = {
|
||||||
|
"trigger": {"conditions": [{"signal": "x", "operator": "==", "value": "1"}]},
|
||||||
|
"path": "3.1 策略 > decision_speed",
|
||||||
|
"sources": [],
|
||||||
|
}
|
||||||
|
normalized = _normalize_rule(rule)
|
||||||
|
assert len(normalized["sources"]) == 1
|
||||||
|
assert normalized["sources"][0]["type"] == "text"
|
||||||
|
assert normalized["sources"][0]["section"] == "3.1 策略"
|
||||||
|
|||||||
@@ -140,9 +140,19 @@ def ir_path(request) -> str:
|
|||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
def ir_data(ir_path: str) -> dict:
|
def ir_data(ir_path: str) -> dict:
|
||||||
"""Load the IR JSON data."""
|
"""Load the IR JSON data, normalizing each rule for defensive schema fixes."""
|
||||||
with open(ir_path, "r", encoding="utf-8") as f:
|
with open(ir_path, "r", encoding="utf-8") as f:
|
||||||
return json.load(f)
|
data = json.load(f)
|
||||||
|
|
||||||
|
# Apply normalize to every rule so old IR files benefit from latest fixes
|
||||||
|
# (invalid source types, missing section fields, trigger nulls, etc.)
|
||||||
|
sys.path.insert(0, str(_PROJECT_ROOT / "skills" / "ir_generation_skill"))
|
||||||
|
from step3_merge_and_audit import _normalize_rule
|
||||||
|
rules = data.get("rules", [])
|
||||||
|
if rules:
|
||||||
|
data["rules"] = [_normalize_rule(r) for r in rules]
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
|
|||||||
Reference in New Issue
Block a user