feat: 新增风险图谱算法与系统仪表盘及操作反馈体系
后端新增风险图谱算法模块、风险观察与反馈服务、规则 DSL 校验器和可解释性引擎,完善系统仪表盘和财务仪表盘统计, 优化 agent 运行和编排执行链路,清理旧开发文档,前端新增 系统趋势、负载热力图等多种仪表盘图表组件,完善操作反馈 对话框和工作台日期选择器,优化报销创建和审批详情交互, 补充单元测试覆盖。
This commit is contained in:
@@ -257,6 +257,130 @@ def build_risk_rule_flow_diagram_details(
|
||||
}
|
||||
|
||||
|
||||
def build_risk_rule_flow_diagram_spec(
|
||||
payload: dict[str, Any],
|
||||
*,
|
||||
fields: tuple[RiskRuleFlowDiagramField, ...],
|
||||
domain_label: str,
|
||||
severity: str,
|
||||
severity_label: str,
|
||||
flow_model: dict[str, Any] | None = None,
|
||||
) -> RiskRuleFlowDiagramSpec:
|
||||
model_spec = _spec_from_flow_model(
|
||||
payload,
|
||||
fields=fields,
|
||||
domain_label=domain_label,
|
||||
severity=severity,
|
||||
severity_label=severity_label,
|
||||
flow_model=flow_model or {},
|
||||
)
|
||||
if model_spec:
|
||||
return model_spec
|
||||
metadata = payload.get("metadata") if isinstance(payload.get("metadata"), dict) else {}
|
||||
flow = metadata.get("flow") if isinstance(metadata.get("flow"), dict) else {}
|
||||
details = build_risk_rule_flow_diagram_details(payload, list(fields))
|
||||
summary = str(metadata.get("condition_summary") or "").strip()
|
||||
return RiskRuleFlowDiagramSpec(
|
||||
title=str(payload.get("name") or "").strip() or "风险规则判断流程",
|
||||
domain_label=domain_label,
|
||||
severity=severity,
|
||||
severity_label=severity_label,
|
||||
fields=fields,
|
||||
start=str(flow.get("start") or "").strip() or "业务单据提交",
|
||||
evidence=str(flow.get("evidence") or "").strip() or "读取规则字段",
|
||||
decision=str(flow.get("decision") or "").strip() or summary or "判断是否命中风险",
|
||||
basis=summary or str(flow.get("decision") or "").strip() or "根据规则字段判断",
|
||||
pass_text=str(flow.get("pass") or "").strip() or "未命中风险,继续流转",
|
||||
fail_text=str(flow.get("fail") or "").strip() or f"命中{severity_label},进入人工复核",
|
||||
fact_lines=details["fact_lines"],
|
||||
condition_lines=details["condition_lines"],
|
||||
hit_logic=str(details["hit_logic"] or ""),
|
||||
)
|
||||
|
||||
|
||||
def _spec_from_flow_model(
|
||||
payload: dict[str, Any],
|
||||
*,
|
||||
fields: tuple[RiskRuleFlowDiagramField, ...],
|
||||
domain_label: str,
|
||||
severity: str,
|
||||
severity_label: str,
|
||||
flow_model: dict[str, Any],
|
||||
) -> RiskRuleFlowDiagramSpec | None:
|
||||
nodes = flow_model.get("nodes") if isinstance(flow_model, dict) else []
|
||||
if not isinstance(nodes, list) or not nodes:
|
||||
return None
|
||||
by_type: dict[str, list[dict[str, Any]]] = {}
|
||||
for node in nodes:
|
||||
if isinstance(node, dict):
|
||||
by_type.setdefault(str(node.get("type") or "").strip(), []).append(node)
|
||||
decisions = by_type.get("decision") or []
|
||||
if not decisions:
|
||||
return None
|
||||
start = _node_description(by_type.get("start"), "业务单据提交")
|
||||
evidence = _node_description(by_type.get("evidence"), "读取规则字段")
|
||||
pass_text = _node_description(by_type.get("pass"), "未命中风险,继续流转")
|
||||
fail_text = _node_description(by_type.get("risk"), f"命中{severity_label},进入人工复核")
|
||||
condition_lines = _condition_lines_from_flow_nodes(decisions)
|
||||
basis = condition_lines[0] if condition_lines else _node_description(decisions, "判断是否命中风险")
|
||||
return RiskRuleFlowDiagramSpec(
|
||||
title=str(payload.get("name") or "").strip() or "风险规则判断流程",
|
||||
domain_label=domain_label,
|
||||
severity=severity,
|
||||
severity_label=severity_label,
|
||||
fields=fields,
|
||||
start=start,
|
||||
evidence=evidence,
|
||||
decision=_node_description(decisions, basis),
|
||||
basis=basis,
|
||||
pass_text=pass_text,
|
||||
fail_text=fail_text,
|
||||
fact_lines=tuple(_field_lines_from_flow_nodes(by_type.get("evidence"), fields)),
|
||||
condition_lines=tuple(condition_lines),
|
||||
hit_logic=_hit_logic_from_flow_model(flow_model, condition_lines),
|
||||
)
|
||||
|
||||
|
||||
def _node_description(nodes: list[dict[str, Any]] | None, fallback: str) -> str:
|
||||
node = nodes[0] if nodes else {}
|
||||
return str(node.get("description") or node.get("title") or fallback).strip()
|
||||
|
||||
|
||||
def _condition_lines_from_flow_nodes(nodes: list[dict[str, Any]]) -> list[str]:
|
||||
visible = [
|
||||
f"{str(node.get('title') or node.get('id') or '判断').strip()}: {str(node.get('description') or '').strip()}"
|
||||
for node in nodes[:4]
|
||||
]
|
||||
if len(nodes) > 4:
|
||||
visible[-1] = f"{visible[-1]};另有 {len(nodes) - 4} 个判断节点按命中逻辑汇总"
|
||||
return visible
|
||||
|
||||
|
||||
def _field_lines_from_flow_nodes(
|
||||
nodes: list[dict[str, Any]] | None,
|
||||
fields: tuple[RiskRuleFlowDiagramField, ...],
|
||||
) -> list[str]:
|
||||
field_keys = _read_string_list((nodes[0] if nodes else {}).get("fields"))
|
||||
if not field_keys:
|
||||
return [
|
||||
f"{chr(65 + index)}={field.label or field.key}[{field.key}]"
|
||||
for index, field in enumerate(fields[:4])
|
||||
]
|
||||
label_by_key = {field.key: field.label or field.key for field in fields}
|
||||
return [
|
||||
f"{chr(65 + index)}={label_by_key.get(key, key)}[{key}]"
|
||||
for index, key in enumerate(field_keys[:4])
|
||||
]
|
||||
|
||||
|
||||
def _hit_logic_from_flow_model(flow_model: dict[str, Any], condition_lines: list[str]) -> str:
|
||||
metadata = flow_model.get("metadata") if isinstance(flow_model.get("metadata"), dict) else {}
|
||||
logic = str(metadata.get("hit_logic") or "").strip()
|
||||
if logic:
|
||||
return logic
|
||||
return " AND ".join(line.split(":", 1)[0] for line in condition_lines[:4] if line)
|
||||
|
||||
|
||||
def _build_fact_lines(
|
||||
facts: list[Any],
|
||||
fields: list[RiskRuleFlowDiagramField],
|
||||
@@ -313,6 +437,15 @@ def _format_condition(condition: dict[str, Any], label_by_key: dict[str, str], i
|
||||
start = _field_group(condition.get("range_start_fields"), label_by_key)
|
||||
end = _field_group(condition.get("range_end_fields"), label_by_key)
|
||||
return f"{prefix}{dates} 不在 [{start}, {end}]"
|
||||
if operator == "numeric_compare":
|
||||
left = _field_group(condition.get("left_fields") or condition.get("fields"), label_by_key)
|
||||
right = _field_group(condition.get("right_fields"), label_by_key)
|
||||
compare = str(condition.get("compare") or "gt").strip().upper()
|
||||
target = right or str(condition.get("threshold") or condition.get("value") or "阈值").strip()
|
||||
return f"{prefix}{left} {compare} {target}"
|
||||
if operator == "duplicate_value":
|
||||
fields = _field_group(condition.get("fields"), label_by_key)
|
||||
return f"{prefix}{fields} 出现重复值"
|
||||
if operator in {"contains_any", "not_contains_any"}:
|
||||
fields = _field_group(condition.get("fields"), label_by_key)
|
||||
keywords = "、".join(_read_string_list(condition.get("keywords"))[:4])
|
||||
|
||||
Reference in New Issue
Block a user