feat: 新增风险图谱算法与系统仪表盘及操作反馈体系

后端新增风险图谱算法模块、风险观察与反馈服务、规则 DSL
校验器和可解释性引擎,完善系统仪表盘和财务仪表盘统计,
优化 agent 运行和编排执行链路,清理旧开发文档,前端新增
系统趋势、负载热力图等多种仪表盘图表组件,完善操作反馈
对话框和工作台日期选择器,优化报销创建和审批详情交互,
补充单元测试覆盖。
This commit is contained in:
caoxiaozhu
2026-05-30 15:46:51 +08:00
parent 4c59941ec6
commit 7989f3a159
314 changed files with 30073 additions and 20626 deletions

View File

@@ -1,7 +1,7 @@
from __future__ import annotations
import re
from datetime import UTC, datetime, timedelta
from datetime import UTC, datetime
from decimal import Decimal, InvalidOperation
from sqlalchemy import select
@@ -19,6 +19,7 @@ from app.services.document_numbering import (
build_document_number,
generate_unique_expense_claim_no,
)
from app.services.user_agent_application_dates import expand_application_time_with_days
from app.services.user_agent_application_locations import normalize_application_location
APPLICATION_CONTEXT_VALUES = {
@@ -160,11 +161,10 @@ class UserAgentApplicationMixin:
manager_name = str(facts.get("manager_name") or "").strip() or "直属领导"
return "\n\n".join(
[
f"当前操作已完成,单据已经推送给 {manager_name} 进行审核,请耐心等待",
"申请单据已生成,并已进入审批流程",
f"系统已推送给 {manager_name} 审核,当前节点:{manager_name}审核中。",
f"申请单号:{application_no}",
"申请信息:\n" + self._build_application_summary_table(facts),
f"当前状态:{manager_name}审核中。",
"费用预估:预计费用已随申请提交,等待领导审核确认。",
"下方是简要单据信息。需要查看完整详情时,请点击快捷方式进入单据详情。",
]
)
@@ -217,6 +217,7 @@ class UserAgentApplicationMixin:
facts["time"] = self._expand_application_time_with_days(
facts.get("time", ""),
facts.get("days", ""),
payload.context_json or {},
)
return facts
@@ -467,81 +468,16 @@ class UserAgentApplicationMixin:
return text
@staticmethod
def _expand_application_time_with_days(time_text: str, days_text: str) -> str:
normalized_time = str(time_text or "").strip()
if not normalized_time or re.search(r"\s*(?:至|到|~|-{2,}|—)\s*", normalized_time):
return normalized_time
days = UserAgentApplicationMixin._resolve_application_days_count(days_text)
if not days:
return normalized_time
match = re.search(
r"(?P<date>20\d{2}[-/.年]\d{1,2}[-/.月]\d{1,2}日?)",
normalized_time,
def _expand_application_time_with_days(
time_text: str,
days_text: str,
context_json: dict[str, object] | None = None,
) -> str:
return expand_application_time_with_days(
time_text,
days_text,
context_json=context_json or {},
)
if not match:
return normalized_time
parsed_start = UserAgentApplicationMixin._parse_application_date(match.group("date"))
if parsed_start is None:
return normalized_time
end_date = parsed_start + timedelta(days=days)
return f"{parsed_start:%Y-%m-%d}{end_date:%Y-%m-%d}"
@staticmethod
def _resolve_application_days_count(days_text: str) -> int:
text = str(days_text or "").strip()
if not text:
return 0
digit_match = re.search(r"\d+", text)
if digit_match:
return max(0, int(digit_match.group(0)))
chinese_match = re.search(r"[一二两三四五六七八九十]{1,3}", text)
if not chinese_match:
return 0
return UserAgentApplicationMixin._parse_chinese_number(chinese_match.group(0))
@staticmethod
def _parse_chinese_number(value: str) -> int:
digits = {
"": 1,
"": 2,
"": 2,
"": 3,
"": 4,
"": 5,
"": 6,
"": 7,
"": 8,
"": 9,
}
text = str(value or "").strip()
if not text:
return 0
if text == "":
return 10
if "" in text:
left, _, right = text.partition("")
tens = digits.get(left, 1) if left else 1
ones = digits.get(right, 0) if right else 0
return tens * 10 + ones
return digits.get(text, 0)
@staticmethod
def _parse_application_date(value: str) -> datetime | None:
normalized = str(value or "").strip().rstrip("").replace("", "-").replace("", "-")
normalized = normalized.replace("/", "-").replace(".", "-")
parts = [part for part in normalized.split("-") if part]
if len(parts) != 3:
return None
try:
year, month, day = (int(part) for part in parts)
return datetime(year, month, day)
except ValueError:
return None
def _resolve_application_amount(
self,