2026-05-26 09:15:14 +08:00
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
2026-05-30 15:46:51 +08:00
|
|
|
|
from typing import Any
|
|
|
|
|
|
|
2026-05-26 09:15:14 +08:00
|
|
|
|
COMPOSITE_RULE_TEMPLATE_KEY = "composite_rule_v1"
|
|
|
|
|
|
|
|
|
|
|
|
COMPOSITE_RULE_OPERATORS = {
|
|
|
|
|
|
"exists_any",
|
|
|
|
|
|
"exists_all",
|
|
|
|
|
|
"all_present",
|
|
|
|
|
|
"in_scope",
|
|
|
|
|
|
"not_in_scope",
|
|
|
|
|
|
"not_in_set",
|
|
|
|
|
|
"overlap",
|
|
|
|
|
|
"not_overlap",
|
|
|
|
|
|
"date_outside_range",
|
2026-05-30 15:46:51 +08:00
|
|
|
|
"numeric_compare",
|
|
|
|
|
|
"duplicate_value",
|
2026-05-26 09:15:14 +08:00
|
|
|
|
"contains_any",
|
|
|
|
|
|
"not_contains_any",
|
|
|
|
|
|
}
|
2026-05-30 15:46:51 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def build_dsl_from_semantic_plan(semantic_plan: dict[str, Any]) -> dict[str, Any]:
|
|
|
|
|
|
"""把模型语义计划转换成可交给 validator 继续规范化的 DSL 草稿。"""
|
|
|
|
|
|
|
|
|
|
|
|
if not isinstance(semantic_plan, dict):
|
|
|
|
|
|
return {}
|
|
|
|
|
|
text_parts = _semantic_text_parts(semantic_plan)
|
|
|
|
|
|
field_keys = _semantic_field_keys(semantic_plan)
|
|
|
|
|
|
if not text_parts and not field_keys:
|
|
|
|
|
|
return {}
|
|
|
|
|
|
return {
|
|
|
|
|
|
"template_key": COMPOSITE_RULE_TEMPLATE_KEY,
|
|
|
|
|
|
"field_keys": field_keys,
|
|
|
|
|
|
"description": str(semantic_plan.get("rule_intent") or "").strip(),
|
|
|
|
|
|
"condition_summary": ";".join(text_parts)[:800],
|
|
|
|
|
|
"keywords": [],
|
|
|
|
|
|
"rule_ir": {
|
|
|
|
|
|
"facts": field_keys,
|
|
|
|
|
|
"conditions": text_parts,
|
|
|
|
|
|
"hit_logic": "由 DSL validator 根据字段本体和语义步骤生成受控条件",
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _semantic_text_parts(semantic_plan: dict[str, Any]) -> list[str]:
|
|
|
|
|
|
parts: list[str] = []
|
|
|
|
|
|
for key in ("rule_intent", "scope", "judgment_steps", "exception_conditions", "risk_action"):
|
|
|
|
|
|
parts.extend(_flatten_semantic_text(semantic_plan.get(key)))
|
|
|
|
|
|
return [item for index, item in enumerate(parts) if item and item not in parts[:index]]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _semantic_field_keys(semantic_plan: dict[str, Any]) -> list[str]:
|
|
|
|
|
|
keys: list[str] = []
|
|
|
|
|
|
for value in (semantic_plan.get("required_fields"), semantic_plan.get("fields")):
|
|
|
|
|
|
for item in value if isinstance(value, list) else []:
|
|
|
|
|
|
key = item if isinstance(item, str) else next(
|
|
|
|
|
|
(item.get(name) for name in ("field", "key", "field_key") if isinstance(item, dict) and item.get(name)),
|
|
|
|
|
|
"",
|
|
|
|
|
|
)
|
|
|
|
|
|
text = str(key or "").strip()
|
|
|
|
|
|
if "." in text and text not in keys:
|
|
|
|
|
|
keys.append(text)
|
|
|
|
|
|
return keys
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _flatten_semantic_text(value: Any) -> list[str]:
|
|
|
|
|
|
if isinstance(value, str):
|
|
|
|
|
|
return [value.strip()] if value.strip() else []
|
|
|
|
|
|
if isinstance(value, list):
|
|
|
|
|
|
return [item for value_item in value for item in _flatten_semantic_text(value_item)]
|
|
|
|
|
|
if isinstance(value, dict):
|
|
|
|
|
|
return [item for value_item in value.values() for item in _flatten_semantic_text(value_item)]
|
|
|
|
|
|
return []
|