feat: 新增预算中心本体与风险规则评分回填

后端新增预算本体解析模块和风险规则评分回填服务,优化规则
生成本体对齐和提示词构建,增强费用类型关键词和本体验证,
完善报销查询和审计接口,前端预算中心页面增加对话框和本
体工具函数,重构审计页面元数据和视图模型,补充单元测试。
This commit is contained in:
caoxiaozhu
2026-05-26 12:16:20 +08:00
parent 0e861d8fa6
commit e1e515ecae
53 changed files with 4350 additions and 921 deletions

View File

@@ -3,16 +3,15 @@ from __future__ import annotations
from collections.abc import Generator
import pytest
from fastapi.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy.orm import Session, sessionmaker
from sqlalchemy.pool import StaticPool
from app.api.deps import get_db
from app.db.base import Base
from app.main import create_app
from app.schemas.ontology import OntologyParseRequest
from app.services.ontology import LlmOntologyParseResult, SemanticOntologyService
from fastapi.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy.orm import Session, sessionmaker
from sqlalchemy.pool import StaticPool
from app.api.deps import get_db
from app.db.base import Base
from app.schemas.ontology import OntologyParseRequest
from app.services.ontology import LlmOntologyParseResult, SemanticOntologyService
def build_session_factory() -> sessionmaker[Session]:
@@ -25,9 +24,11 @@ def build_session_factory() -> sessionmaker[Session]:
return sessionmaker(bind=engine, autoflush=False, autocommit=False)
def build_client() -> tuple[TestClient, sessionmaker[Session]]:
session_factory = build_session_factory()
app = create_app()
def build_client() -> tuple[TestClient, sessionmaker[Session]]:
session_factory = build_session_factory()
from app.main import create_app
app = create_app()
def override_db() -> Generator[Session, None, None]:
db = session_factory()
@@ -253,13 +254,113 @@ def test_semantic_ontology_service_extracts_entities_time_and_constraints() -> N
user_id="pytest",
)
)
assert result.scenario == "expense"
assert result.scenario == "expense"
assert result.intent == "query"
assert result.time_range.start_date == "2026-04-01"
assert result.time_range.end_date == "2026-04-30"
def test_semantic_ontology_service_extracts_budget_query_fields() -> None:
session_factory = build_session_factory()
with session_factory() as db:
result = SemanticOntologyService(db).parse(
OntologyParseRequest(
query="查询 CC-4100 2026年度差旅费可用预算和预算占用",
user_id="pytest",
)
)
entity_map = {item.type: item.normalized_value for item in result.entities}
metric_names = {item.name for item in result.metrics}
assert result.scenario == "budget"
assert result.intent == "query"
assert entity_map["cost_center"] == "CC-4100"
assert entity_map["budget_period"] == "2026年度"
assert entity_map["budget_subject"] == "travel"
assert entity_map["expense_type"] == "travel"
assert {"available_amount", "reserved_amount"}.issubset(metric_names)
def test_semantic_ontology_service_extracts_budget_edit_fields() -> None:
session_factory = build_session_factory()
with session_factory() as db:
result = SemanticOntologyService(db).parse(
OntologyParseRequest(
query="编辑预算2026年度 CC-4100 差旅费预算金额60万元预警线80%,控制动作提醒",
user_id="pytest",
context_json={
"document_type": "budget_plan",
"entry_source": "budget_center",
"conversation_scenario": "budget",
},
)
)
entity_map = {item.type: item.normalized_value for item in result.entities}
assert result.scenario == "budget"
assert result.intent == "draft"
assert result.permission.level == "draft_write"
assert entity_map["budget_period"] == "2026年度"
assert entity_map["budget_subject"] == "travel"
assert entity_map["expense_type"] == "travel"
assert entity_map["budget_amount"] == "600000"
assert entity_map["warning_threshold"] == "80%"
assert entity_map["control_action"] == "remind"
def test_semantic_ontology_service_extracts_quarter_budget_period() -> None:
session_factory = build_session_factory()
with session_factory() as db:
result = SemanticOntologyService(db).parse(
OntologyParseRequest(
query="查询 CC-4100 2026年Q3 住宿费预算金额",
user_id="pytest",
)
)
entity_map = {item.type: item.normalized_value for item in result.entities}
assert result.scenario == "budget"
assert entity_map["budget_period"] == "2026年Q3"
assert entity_map["budget_subject"] == "hotel"
assert entity_map["expense_type"] == "hotel"
@pytest.mark.parametrize(
"query,expected_code,expected_label",
[
("查询2026年度市场推广费预算余额", "marketing", "市场推广费"),
("查看2026年度软件服务费已占用金额", "software", "软件服务费"),
("统计2026年度业务招待费预算金额", "meal", "业务招待费"),
],
)
def test_semantic_ontology_service_links_budget_subject_to_expense_type(
query: str,
expected_code: str,
expected_label: str,
) -> None:
session_factory = build_session_factory()
with session_factory() as db:
result = SemanticOntologyService(db).parse(
OntologyParseRequest(query=query, user_id="pytest")
)
assert result.scenario == "budget"
assert any(
item.type == "budget_subject" and item.normalized_value == expected_code
for item in result.entities
)
assert any(
item.type == "expense_type"
and item.normalized_value == expected_code
and item.value == expected_label
for item in result.entities
)
def test_semantic_ontology_service_extracts_new_document_numbers() -> None:
session_factory = build_session_factory()
with session_factory() as db: