feat: 添加风险规则及 agent assets 功能增强

This commit is contained in:
caoxiaozhu
2026-05-19 16:19:03 +00:00
parent d460ee0fe7
commit 54ffef66d3
52 changed files with 26036 additions and 25171 deletions

View File

@@ -0,0 +1,32 @@
{
"schema_version": "1.0",
"rule_code": "risk.expense.consecutive_transport_receipts",
"name": "连号交通票据",
"enabled": true,
"risk_dimension": "consecutive_receipts",
"ontology_signal": "consecutive_transport_receipts",
"evaluator": "consecutive_transport_receipts",
"applies_to": {
"expense_types": ["transport", "travel"],
"min_attachments": 2
},
"inputs": {
"invoice_no": "attachment.invoice_no"
},
"params": {
"min_consecutive_count": 3
},
"outcomes": {
"pass": { "severity": "none", "action": "continue" },
"fail": {
"severity": "medium",
"action": "manual_review"
}
},
"metadata": {
"owner": "风控与审计部",
"stability": "platform_builtin",
"source_ref": "常用risk.txt / 三、车辆交通 / 连号票集中报销",
"updated_at": "2026-05-19"
}
}

View File

@@ -0,0 +1,29 @@
{
"schema_version": "1.0",
"rule_code": "risk.expense.entertainment_missing_detail",
"name": "招待费事由不完整",
"enabled": true,
"risk_dimension": "entertainment_detail",
"ontology_signal": "entertainment_missing_detail",
"evaluator": "entertainment_reason_missing",
"applies_to": {
"domains": ["meal"]
},
"inputs": {
"reason": "claim.reason_corpus"
},
"params": {},
"outcomes": {
"pass": { "severity": "none", "action": "continue" },
"fail": {
"severity": "medium",
"action": "warn"
}
},
"metadata": {
"owner": "风控与审计部",
"stability": "platform_builtin",
"source_ref": "常用risk.txt / 三、餐费招待 / 业务招待无事由对象",
"updated_at": "2026-05-19"
}
}

View File

@@ -0,0 +1,30 @@
{
"schema_version": "1.0",
"rule_code": "risk.expense.meal_localized_as_travel",
"name": "同城餐饮混入差旅",
"enabled": true,
"risk_dimension": "meal_travel_mix",
"ontology_signal": "meal_as_travel",
"evaluator": "meal_as_travel_same_city",
"applies_to": {
"domains": ["travel"]
},
"inputs": {
"declared": "claim.location",
"meal_city": "attachment.cities"
},
"params": {},
"outcomes": {
"pass": { "severity": "none", "action": "continue" },
"fail": {
"severity": "medium",
"action": "warn"
}
},
"metadata": {
"owner": "风控与审计部",
"stability": "platform_builtin",
"source_ref": "常用risk.txt / 三、餐费招待 / 同城餐饮归集异地差旅",
"updated_at": "2026-05-19"
}
}

View File

@@ -0,0 +1,29 @@
{
"schema_version": "1.0",
"rule_code": "risk.expense.reason_too_brief",
"name": "报销事由过短",
"enabled": true,
"risk_dimension": "reason_quality",
"ontology_signal": "reason_too_brief",
"evaluator": "reason_too_brief",
"applies_to": {},
"inputs": {
"reason": "claim.reason_corpus"
},
"params": {
"min_reason_length": 6
},
"outcomes": {
"pass": { "severity": "none", "action": "continue" },
"fail": {
"severity": "medium",
"action": "warn"
}
},
"metadata": {
"owner": "风控与审计部",
"stability": "platform_builtin",
"source_ref": "常用risk.txt / 通用 / 事由不足以支撑真实性判断",
"updated_at": "2026-05-19"
}
}

View File

@@ -0,0 +1,32 @@
{
"schema_version": "1.0",
"rule_code": "risk.invoice.claimant_buyer_name_match",
"name": "报销人与发票抬头一致",
"enabled": true,
"risk_dimension": "identity_consistency",
"ontology_signal": "buyer_name_mismatch",
"evaluator": "identity_consistency",
"applies_to": {
"min_attachments": 1
},
"inputs": {
"claimant": "claim.employee_name",
"buyer": "attachment.buyer_name"
},
"params": {
"allow_keywords": ["代报", "集团", "公司", "有限公司"]
},
"outcomes": {
"pass": { "severity": "none", "action": "continue" },
"fail": {
"severity": "high",
"action": "manual_review"
}
},
"metadata": {
"owner": "风控与审计部",
"stability": "platform_builtin",
"source_ref": "常用risk.txt / 二、发票类 / 抬头错误",
"updated_at": "2026-05-19"
}
}

View File

@@ -0,0 +1,30 @@
{
"schema_version": "1.0",
"rule_code": "risk.invoice.cross_year_invoice",
"name": "跨年发票入账",
"enabled": true,
"risk_dimension": "cross_year_invoice",
"ontology_signal": "cross_year_invoice",
"evaluator": "cross_year_invoice",
"applies_to": {
"min_attachments": 1
},
"inputs": {
"invoice_date": "attachment.invoice_date",
"claim_date": ["claim.occurred_at", "item.item_date"]
},
"params": {},
"outcomes": {
"pass": { "severity": "none", "action": "continue" },
"fail": {
"severity": "medium",
"action": "warn"
}
},
"metadata": {
"owner": "风控与审计部",
"stability": "platform_builtin",
"source_ref": "常用risk.txt / 二、发票类 / 跨年发票",
"updated_at": "2026-05-19"
}
}

View File

@@ -0,0 +1,30 @@
{
"schema_version": "1.0",
"rule_code": "risk.invoice.document_expense_mismatch",
"name": "开票内容与报销场景不符",
"enabled": true,
"risk_dimension": "document_expense_mismatch",
"ontology_signal": "document_expense_mismatch",
"evaluator": "document_expense_mismatch",
"applies_to": {
"min_attachments": 1
},
"inputs": {
"document_type": "attachment.document_type",
"expense_type": ["claim.expense_type", "item.item_type"]
},
"params": {},
"outcomes": {
"pass": { "severity": "none", "action": "continue" },
"fail": {
"severity": "medium",
"action": "warn"
}
},
"metadata": {
"owner": "风控与审计部",
"stability": "platform_builtin",
"source_ref": "常用risk.txt / 二、发票类 / 开票内容与业务不符",
"updated_at": "2026-05-19"
}
}

View File

@@ -0,0 +1,29 @@
{
"schema_version": "1.0",
"rule_code": "risk.invoice.duplicate_invoice",
"name": "发票重复报销",
"enabled": true,
"risk_dimension": "duplicate_invoice",
"ontology_signal": "duplicate_invoice",
"evaluator": "duplicate_invoice",
"applies_to": {
"min_attachments": 1
},
"inputs": {
"invoice_no": "attachment.invoice_no"
},
"params": {},
"outcomes": {
"pass": { "severity": "none", "action": "continue" },
"fail": {
"severity": "high",
"action": "block"
}
},
"metadata": {
"owner": "风控与审计部",
"stability": "platform_builtin",
"source_ref": "常用risk.txt / 二、发票类 / 重复报销",
"updated_at": "2026-05-19"
}
}

View File

@@ -0,0 +1,30 @@
{
"schema_version": "1.0",
"rule_code": "risk.invoice.vague_goods_description",
"name": "发票品名过于笼统",
"enabled": true,
"risk_dimension": "vague_goods_description",
"ontology_signal": "vague_goods_description",
"evaluator": "vague_goods_description",
"applies_to": {
"expense_types": ["office", "other"],
"min_attachments": 1
},
"inputs": {
"ocr": "attachment.ocr_text"
},
"params": {},
"outcomes": {
"pass": { "severity": "none", "action": "continue" },
"fail": {
"severity": "medium",
"action": "warn"
}
},
"metadata": {
"owner": "风控与审计部",
"stability": "platform_builtin",
"source_ref": "常用risk.txt / 二、发票类 / 品名笼统",
"updated_at": "2026-05-19"
}
}

View File

@@ -0,0 +1,30 @@
{
"schema_version": "1.0",
"rule_code": "risk.invoice.void_or_red_invoice",
"name": "作废或红冲发票",
"enabled": true,
"risk_dimension": "void_or_red_invoice",
"ontology_signal": "void_or_red_invoice",
"evaluator": "invoice_void_or_red",
"applies_to": {
"min_attachments": 1
},
"inputs": {
"status": "attachment.invoice_status",
"ocr": "attachment.ocr_text"
},
"params": {},
"outcomes": {
"pass": { "severity": "none", "action": "continue" },
"fail": {
"severity": "high",
"action": "block"
}
},
"metadata": {
"owner": "风控与审计部",
"stability": "platform_builtin",
"source_ref": "常用risk.txt / 二、发票类 / 作废红冲发票",
"updated_at": "2026-05-19"
}
}

View File

@@ -0,0 +1,30 @@
{
"schema_version": "1.0",
"rule_code": "risk.travel.base_location_overlap",
"name": "常驻地重合出差风险",
"enabled": true,
"risk_dimension": "base_location_overlap",
"ontology_signal": "base_location_overlap",
"evaluator": "base_location_overlap",
"applies_to": {
"domains": ["travel"]
},
"inputs": {
"employee_base": "employee.location",
"declared": "claim.location"
},
"params": {},
"outcomes": {
"pass": { "severity": "none", "action": "continue" },
"fail": {
"severity": "high",
"action": "manual_review"
}
},
"metadata": {
"owner": "风控与审计部",
"stability": "platform_builtin",
"source_ref": "常用risk.txt / 一、出差类 / 两头在外",
"updated_at": "2026-05-19"
}
}

View File

@@ -0,0 +1,29 @@
{
"schema_version": "1.0",
"rule_code": "risk.travel.destination_receipt_location",
"name": "申报地点与票据地点一致",
"risk_dimension": "location_consistency",
"ontology_signal": "location_mismatch",
"evaluator": "location_consistency",
"inputs": {
"declared": "claim.location",
"evidence": ["attachment.cities", "item.item_location"]
},
"params": {
"match_mode": "city_fuzzy",
"missing_evidence": "warn"
},
"outcomes": {
"pass": { "severity": "none", "action": "continue" },
"fail": {
"severity": "high",
"action": "manual_review",
"message_template": "申报地点 {declared} 与票据识别地点 {evidence} 不一致"
}
},
"metadata": {
"owner": "风控与审计部",
"stability": "platform_builtin",
"updated_at": "2026-05-18"
}
}

View File

@@ -0,0 +1,32 @@
{
"schema_version": "1.0",
"rule_code": "risk.travel.hotel_without_itinerary",
"name": "住宿城市与行程不一致",
"enabled": true,
"risk_dimension": "hotel_itinerary",
"ontology_signal": "hotel_itinerary_mismatch",
"evaluator": "hotel_without_itinerary",
"applies_to": {
"domains": ["travel"],
"expense_types": ["hotel", "travel"]
},
"inputs": {
"declared": "claim.location",
"hotel": "attachment.hotel_city",
"itinerary": "attachment.route_cities"
},
"params": {},
"outcomes": {
"pass": { "severity": "none", "action": "continue" },
"fail": {
"severity": "high",
"action": "manual_review"
}
},
"metadata": {
"owner": "风控与审计部",
"stability": "platform_builtin",
"source_ref": "常用risk.txt / 三、住宿费 / 夜间异地住宿、酒店连续多天",
"updated_at": "2026-05-19"
}
}

View File

@@ -0,0 +1,30 @@
{
"schema_version": "1.0",
"rule_code": "risk.travel.intracity_travel_claim",
"name": "同城虚报差旅补贴",
"enabled": true,
"risk_dimension": "intracity_travel",
"ontology_signal": "intracity_travel",
"evaluator": "intracity_travel_claim",
"applies_to": {
"domains": ["travel"]
},
"inputs": {
"declared": "claim.location",
"evidence": ["attachment.route", "attachment.cities"]
},
"params": {},
"outcomes": {
"pass": { "severity": "none", "action": "continue" },
"fail": {
"severity": "high",
"action": "manual_review"
}
},
"metadata": {
"owner": "风控与审计部",
"stability": "platform_builtin",
"source_ref": "常用risk.txt / 一、出差类 / 同城虚报差旅",
"updated_at": "2026-05-19"
}
}

View File

@@ -0,0 +1,30 @@
{
"schema_version": "1.0",
"rule_code": "risk.travel.multi_city_reason_required",
"name": "多城市行程需说明",
"enabled": true,
"risk_dimension": "multi_city_itinerary",
"ontology_signal": "multi_city_itinerary",
"evaluator": "multi_city_reason_required",
"applies_to": {
"domains": ["travel"]
},
"inputs": {
"reason": "claim.reason_corpus",
"cities": ["attachment.cities", "item.item_location"]
},
"params": {},
"outcomes": {
"pass": { "severity": "none", "action": "continue" },
"fail": {
"severity": "medium",
"action": "warn"
}
},
"metadata": {
"owner": "风控与审计部",
"stability": "platform_builtin",
"source_ref": "常用risk.txt / 一、出差类 / 绕道出行、行程不符",
"updated_at": "2026-05-19"
}
}

View File

@@ -0,0 +1,28 @@
#!/usr/bin/env python3
"""Sync platform risk rule assets from server/rules/risk-rules/*.json."""
from __future__ import annotations
import sys
from pathlib import Path
SERVER_SRC = Path(__file__).resolve().parents[1] / "src"
if str(SERVER_SRC) not in sys.path:
sys.path.insert(0, str(SERVER_SRC))
from app.db.session import get_session_factory # noqa: E402
from app.services.agent_foundation import AgentFoundationService # noqa: E402
def main() -> None:
db = get_session_factory()()
try:
count = AgentFoundationService(db).sync_platform_risk_rules_from_library()
db.commit()
print(f"Synced {count} risk rule manifest(s) from library.")
finally:
db.close()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,13 @@
#!/usr/bin/env python3
import json
import urllib.request
base = "http://127.0.0.1:8000/api/v1"
items = json.loads(urllib.request.urlopen(f"{base}/agent-assets?asset_type=rule").read())
risk = next((i for i in items if str(i.get("code", "")).startswith("risk.")), None)
print("risk asset:", risk.get("code") if risk else None)
if not risk:
raise SystemExit(1)
resp = urllib.request.urlopen(f"{base}/agent-assets/{risk['id']}/rule-json")
payload = json.loads(resp.read())
print("rule-json ok:", payload.get("file_name"), payload.get("evaluator"))

View File

@@ -27,7 +27,6 @@ from app.schemas.agent_asset import (
AgentAssetRuleJsonWrite,
AgentAssetSpreadsheetChangeRecordRead,
AgentAssetUpdate,
AgentAssetVersionCompareRead,
AgentAssetVersionCreate,
AgentAssetVersionRead,
AgentAssetVersionTimelineItemRead,
@@ -167,7 +166,7 @@ def get_agent_asset_spreadsheet_onlyoffice_config(
db: DbSession,
version: Annotated[
str | None,
Query(description="可选的规则版本号;不传时默认当前版本"),
Query(description="兼容旧前端的可选参数;表格规则始终打开当前规则表"),
] = None,
) -> AgentAssetOnlyOfficeConfigRead:
try:
@@ -184,7 +183,7 @@ def get_agent_asset_spreadsheet_onlyoffice_config(
"/{asset_id}/spreadsheet/content",
response_class=FileResponse,
summary="下载或预览规则 Excel 文件",
description="按版本返回规则 Excel 快照,用于浏览器预览或下载。",
description="返回当前规则 Excel 文件,用于浏览器预览或下载。",
)
def get_agent_asset_spreadsheet_content(
asset_id: str,
@@ -192,7 +191,7 @@ def get_agent_asset_spreadsheet_content(
db: DbSession,
version: Annotated[
str | None,
Query(description="可选的规则版本号;不传时默认当前版本"),
Query(description="兼容旧前端的可选参数;不传时返回当前规则表"),
] = None,
) -> FileResponse:
try:
@@ -215,18 +214,18 @@ def get_agent_asset_spreadsheet_content(
def get_agent_asset_spreadsheet_onlyoffice_content(
asset_id: str,
db: DbSession,
version: Annotated[
str,
Query(min_length=1, description="规则版本号。"),
],
access_token: Annotated[
str,
Query(min_length=1, description="ONLYOFFICE 临时访问令牌。"),
],
version: Annotated[
str | None,
Query(description="兼容旧 ONLYOFFICE URL当前表格模式不再使用。"),
] = None,
) -> FileResponse:
try:
service = AgentAssetService(db)
service.validate_rule_spreadsheet_access_token(asset_id, version, access_token)
service.validate_rule_spreadsheet_access_token(asset_id, access_token)
file_path, media_type, filename = service.get_rule_spreadsheet_content(
asset_id,
version=version,
@@ -246,7 +245,7 @@ def get_agent_asset_spreadsheet_onlyoffice_content(
response_model=AgentAssetRead,
status_code=status.HTTP_201_CREATED,
summary="上传规则 Excel 文件",
description="为指定规则上传新的 Excel 快照,并自动生成新规则版本",
description="为指定规则上传新的 Excel 文件,并记录本次表格修改",
)
def upload_agent_asset_spreadsheet(
asset_id: str,
@@ -311,16 +310,16 @@ def import_agent_asset_spreadsheet_content(
"/{asset_id}/spreadsheet/onlyoffice/callback",
response_model=AgentAssetOnlyOfficeCallbackRead,
summary="接收规则 Excel 的 ONLYOFFICE 回调",
description="接收 ONLYOFFICE 回写内容,并自动生成新的规则版本",
description="接收 ONLYOFFICE 回写内容,并记录本次表格修改",
)
def handle_agent_asset_spreadsheet_onlyoffice_callback(
asset_id: str,
payload: AgentAssetOnlyOfficeCallbackWrite,
db: DbSession,
version: Annotated[
str,
Query(min_length=1, description="打开编辑器时对应的规则版本号"),
],
str | None,
Query(description="兼容旧 ONLYOFFICE 回调;当前表格模式不再使用"),
] = None,
actor_name: Annotated[
str | None,
Query(description="发起编辑的用户显示名。"),
@@ -601,25 +600,3 @@ def get_agent_asset_version_timeline(
except Exception as exc:
_handle_asset_error(exc)
@router.get(
"/{asset_id}/versions/compare",
response_model=AgentAssetVersionCompareRead,
summary="比较两个规则表版本",
description="对比两个 Excel 规则表版本的工作表变化与单元格级差异。",
)
def compare_agent_asset_spreadsheet_versions(
asset_id: str,
_: CurrentUser,
db: DbSession,
base_version: Annotated[str, Query(min_length=1, description="基准版本号")],
target_version: Annotated[str, Query(min_length=1, description="对比版本号")],
) -> AgentAssetVersionCompareRead:
try:
return AgentAssetService(db).compare_spreadsheet_versions(
asset_id,
base_version=base_version,
target_version=target_version,
)
except Exception as exc:
_handle_asset_error(exc)

View File

@@ -133,22 +133,10 @@ class AgentAssetSpreadsheetDiffSheetRead(BaseModel):
change_type: str
class AgentAssetVersionCompareRead(BaseModel):
base_version: str
target_version: str
added_sheet_count: int = 0
removed_sheet_count: int = 0
changed_sheet_count: int = 0
changed_cell_count: int = 0
sheet_changes: list[AgentAssetSpreadsheetDiffSheetRead] = Field(default_factory=list)
cell_changes: list[AgentAssetSpreadsheetDiffCellRead] = Field(default_factory=list)
class AgentAssetSpreadsheetChangeRecordRead(BaseModel):
id: str
actor: str
changed_at: datetime
version: str | None = None
summary: str
sheet_changes: list[AgentAssetSpreadsheetDiffSheetRead] = Field(default_factory=list)
cell_changes: list[AgentAssetSpreadsheetDiffCellRead] = Field(default_factory=list)

View File

@@ -36,7 +36,6 @@ from app.schemas.agent_asset import (
AgentAssetSpreadsheetDiffCellRead,
AgentAssetSpreadsheetDiffSheetRead,
AgentAssetUpdate,
AgentAssetVersionCompareRead,
AgentAssetVersionCreate,
AgentAssetVersionRead,
AgentAssetVersionTimelineItemRead,
@@ -511,18 +510,16 @@ class AgentAssetService:
return self._build_onlyoffice_spreadsheet_config(
asset_id=asset_id,
current_user=current_user,
resolved_version=resolved_version,
metadata=metadata,
editable=resolved_version == PREVIEW_RULE_CURRENT_VERSION,
)
asset = self._require_spreadsheet_rule(asset_id)
resolved_version, metadata = self._resolve_current_spreadsheet_meta(asset)
_, metadata = self._resolve_current_spreadsheet_meta(asset)
editable = self._can_edit_current_spreadsheet(current_user)
return self._build_onlyoffice_spreadsheet_config(
asset_id=asset.id,
current_user=current_user,
resolved_version=resolved_version,
metadata=metadata,
editable=editable,
)
@@ -555,7 +552,6 @@ class AgentAssetService:
def validate_rule_spreadsheet_access_token(
self,
asset_id: str,
version: str,
access_token: str,
) -> None:
onlyoffice_settings = resolve_onlyoffice_settings()
@@ -571,7 +567,6 @@ class AgentAssetService:
if (
payload.get("scope") != "agent-asset-spreadsheet"
or payload.get("asset_id") != asset_id
or payload.get("version") != version
):
raise ValueError("ONLYOFFICE 文件访问令牌无效。")
@@ -604,7 +599,6 @@ class AgentAssetService:
)
changed_sheet_count = self._count_changed_sheets(sheet_changes, cell_changes)
changed_cell_count = len(cell_changes)
next_version = self._next_available_version(asset)
metadata = self._store_current_rule_spreadsheet(
asset,
@@ -613,45 +607,10 @@ class AgentAssetService:
actor=actor,
source=source,
)
snapshot_metadata = self.spreadsheet_manager.store_rule_library_spreadsheet_snapshot(
library=self._resolve_spreadsheet_rule_library(asset),
asset_id=asset.id,
version=next_version,
file_name=file_name,
content=content,
actor_name=actor,
source=source,
)
operation_label = (
change_note
or (
"ONLYOFFICE 在线编辑"
if source == "onlyoffice"
else f"上传并覆盖当前规则表:{normalized_name}"
)
)
summary = self._build_spreadsheet_change_summary(
operation_label,
sheet_changes,
cell_changes,
)
version_content = self.spreadsheet_manager.build_version_markdown(
rule_name=asset.name,
version=next_version,
metadata=snapshot_metadata,
)
self.create_version(
asset.id,
AgentAssetVersionCreate(
version=next_version,
content=version_content,
content_type=AgentAssetContentType.MARKDOWN,
change_note=summary,
created_by=actor,
),
actor=actor,
request_id=request_id,
)
self.audit_service.log_action(
actor=actor,
action="edit_rule_spreadsheet",
@@ -660,13 +619,11 @@ class AgentAssetService:
before_json={"storage_key": current_metadata.storage_key},
after_json={
"summary": summary,
"version": next_version,
"changed_sheet_count": changed_sheet_count,
"changed_cell_count": changed_cell_count,
"sheet_changes": [item.model_dump() for item in sheet_changes],
"cell_changes": [item.model_dump() for item in cell_changes[:500]],
"storage_key": metadata.storage_key,
"snapshot_storage_key": snapshot_metadata.storage_key,
},
request_id=request_id,
)
@@ -705,7 +662,7 @@ class AgentAssetService:
self,
asset_id: str,
*,
version: str,
version: str | None = None,
payload: dict[str, Any],
actor_name: str | None = None,
) -> None:
@@ -721,8 +678,6 @@ class AgentAssetService:
callback = self._parse_onlyoffice_callback(payload)
if callback.status not in {2, 6} or not callback.download_url:
return
if str(version or "").strip() not in {"", "current", self._resolve_working_version(asset)}:
return
_, current_metadata = self._resolve_current_spreadsheet_meta(asset)
request = Request(
@@ -924,44 +879,6 @@ class AgentAssetService:
return sorted(events, key=lambda item: item.event_time)
def compare_spreadsheet_versions(
self,
asset_id: str,
*,
base_version: str,
target_version: str,
) -> AgentAssetVersionCompareRead:
self._ensure_ready()
asset = self._require_spreadsheet_rule(asset_id)
resolved_base, base_meta = self._resolve_spreadsheet_version_meta(
asset,
version=base_version,
)
resolved_target, target_meta = self._resolve_spreadsheet_version_meta(
asset,
version=target_version,
)
base_workbook = self._load_spreadsheet_for_compare(base_meta)
target_workbook = self._load_spreadsheet_for_compare(target_meta)
sheet_changes, cell_changes = self._collect_workbook_changes(
base_workbook,
target_workbook,
)
added_sheet_count = sum(1 for item in sheet_changes if item.change_type == "added")
removed_sheet_count = sum(1 for item in sheet_changes if item.change_type == "removed")
return AgentAssetVersionCompareRead(
base_version=resolved_base,
target_version=resolved_target,
added_sheet_count=added_sheet_count,
removed_sheet_count=removed_sheet_count,
changed_sheet_count=self._count_changed_sheets(sheet_changes, cell_changes),
changed_cell_count=len(cell_changes),
sheet_changes=sheet_changes,
cell_changes=cell_changes[:500],
)
def list_spreadsheet_change_records(
self,
asset_id: str,
@@ -981,8 +898,7 @@ class AgentAssetService:
id=log.id,
actor=log.actor,
changed_at=log.created_at,
version=str((log.after_json or {}).get("version") or "").strip() or None,
summary=str((log.after_json or {}).get("summary") or "ONLYOFFICE 在线编辑保存。"),
summary=str((log.after_json or {}).get("summary") or "表格内容已保存。"),
sheet_changes=[
AgentAssetSpreadsheetDiffSheetRead.model_validate(item)
for item in ((log.after_json or {}).get("sheet_changes") or [])
@@ -1292,7 +1208,6 @@ class AgentAssetService:
*,
asset_id: str,
current_user: CurrentUserContext,
resolved_version: str,
metadata: RuleSpreadsheetMeta,
editable: bool,
) -> AgentAssetOnlyOfficeConfigRead:
@@ -1307,21 +1222,21 @@ class AgentAssetService:
backend_base_url = onlyoffice_settings.backend_url.rstrip("/")
public_url = onlyoffice_settings.public_url.rstrip("/")
access_token = self._build_onlyoffice_access_token(asset_id, resolved_version)
access_token = self._build_onlyoffice_access_token(asset_id)
document_url = (
f"{backend_base_url}{settings.api_v1_prefix}/agent-assets/{asset_id}/spreadsheet/onlyoffice/content"
f"?version={resolved_version}&access_token={access_token}"
f"?access_token={access_token}"
)
callback_url = (
f"{backend_base_url}{settings.api_v1_prefix}/agent-assets/{asset_id}/spreadsheet/onlyoffice/callback"
f"?version={resolved_version}&actor_name={quote(current_user.name)}"
f"?actor_name={quote(current_user.name)}"
)
config: dict[str, Any] = {
"documentType": "cell",
"document": {
"fileType": Path(metadata.file_name).suffix.lstrip(".").lower() or "xlsx",
"key": self._build_onlyoffice_document_key(asset_id, resolved_version, metadata),
"key": self._build_onlyoffice_document_key(asset_id, metadata),
"title": metadata.file_name,
"url": document_url,
"permissions": {
@@ -1462,19 +1377,6 @@ class AgentAssetService:
major, minor, patch = [int(item) for item in parts]
return f"v{major}.{minor}.{patch + 1}"
@staticmethod
def _can_edit_spreadsheet_version(
asset: AgentAsset,
current_user: CurrentUserContext,
version: str,
) -> bool:
role_codes = {str(item).strip() for item in current_user.role_codes}
can_edit = current_user.is_admin or "manager" in role_codes or "finance" in role_codes
return (
can_edit
and AgentAssetService._resolve_working_version(asset) == str(version or "").strip()
)
@staticmethod
def _can_edit_current_spreadsheet(current_user: CurrentUserContext) -> bool:
role_codes = {str(item).strip() for item in current_user.role_codes}
@@ -1483,23 +1385,21 @@ class AgentAssetService:
@staticmethod
def _build_onlyoffice_document_key(
asset_id: str,
version: str,
metadata: RuleSpreadsheetMeta,
) -> str:
fingerprint = metadata.checksum or metadata.updated_at or metadata.file_name
raw_key = f"{asset_id}-{version}-{fingerprint}"
raw_key = f"{asset_id}-{fingerprint}"
return "".join(
character if character.isalnum() or character in {"-", "_", ".", "="} else "_"
for character in raw_key
)
@staticmethod
def _build_onlyoffice_access_token(asset_id: str, version: str) -> str:
def _build_onlyoffice_access_token(asset_id: str) -> str:
onlyoffice_settings = resolve_onlyoffice_settings()
payload = {
"scope": "agent-asset-spreadsheet",
"asset_id": asset_id,
"version": version,
}
return jwt.encode(payload, onlyoffice_settings.jwt_secret, algorithm="HS256")
@@ -1646,7 +1546,6 @@ class AgentAssetService:
@staticmethod
def _build_spreadsheet_change_summary(
operation_label: str,
sheet_changes: list[AgentAssetSpreadsheetDiffSheetRead],
cell_changes: list[AgentAssetSpreadsheetDiffCellRead],
) -> str:
@@ -1655,15 +1554,15 @@ class AgentAssetService:
| {item.sheet_name for item in cell_changes}
)
if not sheet_names:
return f"{operation_label}文件内容已保存,未发现单元格级差异。"
return "文件内容已保存,未发现单元格级差异。"
preview = "".join(sheet_names[:3])
if len(sheet_names) > 3:
preview = f"{preview}"
sheet_text = f"涉及 {len(sheet_names)} 个工作表({preview}"
if cell_changes:
return f"{operation_label}{sheet_text},共 {len(cell_changes)} 处单元格改动。"
return f"{operation_label}{sheet_text},工作表结构发生变化。"
return f"{sheet_text},共 {len(cell_changes)} 处单元格改动。"
return f"{sheet_text},工作表结构发生变化。"
def _next_available_version(self, asset: AgentAsset) -> str:
candidate = self._increment_version(self._resolve_working_version(asset))

View File

@@ -1,70 +1,70 @@
from __future__ import annotations
import uuid
from typing import Any
from sqlalchemy.orm import Session
from app.core.logging import get_logger
from app.models.audit_log import AuditLog
from app.repositories.audit_log import AuditLogRepository
from app.schemas.audit_log import AuditLogRead
from app.services.agent_foundation import AgentFoundationService
logger = get_logger("app.services.audit")
class AuditLogService:
def __init__(self, db: Session) -> None:
self.db = db
self.repository = AuditLogRepository(db)
def list_logs(
self,
*,
resource_type: str | None = None,
resource_id: str | None = None,
action: str | None = None,
limit: int = 50,
) -> list[AuditLogRead]:
self._ensure_ready()
items = self.repository.list(
resource_type=resource_type,
resource_id=resource_id,
action=action,
limit=limit,
)
return [AuditLogRead.model_validate(item) for item in items]
def log_action(
self,
*,
actor: str,
action: str,
resource_type: str,
resource_id: str,
before_json: dict[str, Any] | None = None,
after_json: dict[str, Any] | None = None,
request_id: str | None = None,
) -> AuditLog:
log = AuditLog(
actor=actor,
action=action,
resource_type=resource_type,
resource_id=resource_id,
before_json=before_json,
after_json=after_json,
request_id=request_id or uuid.uuid4().hex,
)
created = self.repository.create(log)
logger.info(
"Created audit log id=%s action=%s resource=%s:%s",
created.id,
created.action,
created.resource_type,
created.resource_id,
)
return created
def _ensure_ready(self) -> None:
AgentFoundationService(self.db).ensure_foundation_ready()
from __future__ import annotations
import uuid
from typing import Any
from sqlalchemy.orm import Session
from app.core.logging import get_logger
from app.models.audit_log import AuditLog
from app.repositories.audit_log import AuditLogRepository
from app.schemas.audit_log import AuditLogRead
from app.services.agent_foundation import AgentFoundationService
logger = get_logger("app.services.audit")
class AuditLogService:
def __init__(self, db: Session) -> None:
self.db = db
self.repository = AuditLogRepository(db)
def list_logs(
self,
*,
resource_type: str | None = None,
resource_id: str | None = None,
action: str | None = None,
limit: int = 50,
) -> list[AuditLogRead]:
self._ensure_ready()
items = self.repository.list(
resource_type=resource_type,
resource_id=resource_id,
action=action,
limit=limit,
)
return [AuditLogRead.model_validate(item) for item in items]
def log_action(
self,
*,
actor: str,
action: str,
resource_type: str,
resource_id: str,
before_json: dict[str, Any] | None = None,
after_json: dict[str, Any] | None = None,
request_id: str | None = None,
) -> AuditLog:
log = AuditLog(
actor=actor,
action=action,
resource_type=resource_type,
resource_id=resource_id,
before_json=before_json,
after_json=after_json,
request_id=request_id or uuid.uuid4().hex,
)
created = self.repository.create(log)
logger.info(
"Created audit log id=%s action=%s resource=%s:%s",
created.id,
created.action,
created.resource_type,
created.resource_id,
)
return created
def _ensure_ready(self) -> None:
AgentFoundationService(self.db).ensure_foundation_ready()

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -177,16 +177,16 @@ SLOT_LABELS = {
}
DATE_TEXT_PATTERN = re.compile(r"(\d{4}[年/-]\d{1,2}[月/-]\d{1,2}日?)")
AMOUNT_TEXT_PATTERN = re.compile(
r"(\d+(?:\.\d+)?)\s*(?:万元|万员|万圆|万园|万块|万元整|元整|块钱|块|元|员|圆|园|万)"
)
AMOUNT_TEXT_PATTERN = re.compile(
r"(\d+(?:\.\d+)?)\s*(?:万元|万员|万圆|万园|万块|万元整|元整|块钱|块|元|员|圆|园|万)"
)
DOCUMENT_AMOUNT_PATTERN = re.compile(
r"(?:价税合计|合计金额|费用合计|订单(?:总)?金额|支付(?:金额)?|实付(?:金额)?|实收(?:金额)?|总(?:额|计|价)|票价|金额|车费|消费金额)"
r"[:\s¥¥人民币]*([0-9]+(?:[.,][0-9]{1,2})?)"
)
DOCUMENT_CURRENCY_AMOUNT_PATTERN = re.compile(r"[¥¥]\s*([0-9]+(?:[.,][0-9]{1,2})?)")
SOURCE_LABELS = {
SOURCE_LABELS = {
"user_text": "用户描述",
"user_form": "用户修改",
"ocr": "票据识别",
@@ -215,7 +215,7 @@ INFERRED_REASON_LABELS = {
"welfare": "员工福利",
"other": "其他费用",
}
SYSTEM_GENERATED_REASON_PREFIXES = (
SYSTEM_GENERATED_REASON_PREFIXES = (
"我上传了",
"请按当前已识别信息",
"请把当前上传的票据",
@@ -225,20 +225,20 @@ SYSTEM_GENERATED_REASON_PREFIXES = (
"我已修改识别信息",
"查看报销草稿",
"请解释一下当前这笔报销的合规风险和待补充项",
)
AMOUNT_UNIT_ALIASES = {
"": "",
"": "",
"": "",
"": "",
"块钱": "",
"元整": "",
"万员": "万元",
"万圆": "万元",
"万园": "万元",
"万块": "万元",
"万元整": "万元",
}
)
AMOUNT_UNIT_ALIASES = {
"": "",
"": "",
"": "",
"": "",
"块钱": "",
"元整": "",
"万员": "万元",
"万圆": "万元",
"万园": "万元",
"万块": "万元",
"万元整": "万元",
}
class UserAgentService:
@@ -1742,7 +1742,7 @@ class UserAgentService:
if is_submitted:
body = (
f"主题:{subject}\n"
f"结论:报销单已提交,当前节点为 {approval_stage or '审批中'}\n"
f"结论:报销单已提交,当前节点为 {approval_stage or '审批中'}\n"
"建议:后续可在个人报销列表中跟踪审批进度,必要时再补充说明或附件。\n"
f"原始问题:{payload.message}"
)
@@ -2381,7 +2381,7 @@ class UserAgentService:
if review_action == "next_step":
if draft_payload is not None and draft_payload.status == "submitted":
stage_text = draft_payload.approval_stage or "审批中"
return f"报销单 {draft_payload.claim_no or ''} 已提交,当前节点为 {stage_text}".strip()
return f"报销单 {draft_payload.claim_no or ''} 已提交,当前节点为 {stage_text}".strip()
if payload.tool_payload.get("submission_blocked"):
return str(payload.tool_payload.get("message") or "").strip() or "当前报销单暂时还不能提交审批。"
return (
@@ -2947,19 +2947,19 @@ class UserAgentService:
"expense_type_code": "",
}
participants: list[str] = []
for item in payload.ontology.entities:
if item.type == "employee" and not values["employee_name"]:
values["employee_name"] = item.value
elif item.type == "customer" and not values["customer"]:
values["customer"] = item.value
elif item.type == "amount" and item.role != "threshold" and not values["amount"]:
normalized_amount = str(item.normalized_value or "").strip()
values["amount"] = f"{normalized_amount}" if normalized_amount else item.value
elif item.type == "expense_type" and not values["expense_type_code"]:
values["expense_type_code"] = item.normalized_value
values["expense_type"] = EXPENSE_TYPE_LABELS.get(
item.normalized_value,
item.value,
for item in payload.ontology.entities:
if item.type == "employee" and not values["employee_name"]:
values["employee_name"] = item.value
elif item.type == "customer" and not values["customer"]:
values["customer"] = item.value
elif item.type == "amount" and item.role != "threshold" and not values["amount"]:
normalized_amount = str(item.normalized_value or "").strip()
values["amount"] = f"{normalized_amount}" if normalized_amount else item.value
elif item.type == "expense_type" and not values["expense_type_code"]:
values["expense_type_code"] = item.normalized_value
values["expense_type"] = EXPENSE_TYPE_LABELS.get(
item.normalized_value,
item.value,
)
elif item.type in {"participant", "person"} and item.value.strip():
participants.append(item.value.strip())
@@ -3189,7 +3189,24 @@ class UserAgentService:
evidence="来源于用户修改后的结构化表单。",
)
inferred_reason = self._infer_reason_from_claim_groups(
claim_groups=claim_groups,
)
reason_value = self._resolve_reason_text(self._resolve_reason_source_text(payload))
if inferred_reason:
return self._build_slot_value(
value=inferred_reason,
raw_value=reason_value or inferred_reason,
normalized_value=inferred_reason,
source="ocr",
confidence=0.82,
evidence=(
"系统已根据票据识别结果预置场景类型;原始描述仍保留为补充说明。"
if reason_value
else "系统已根据票据识别场景补全通用事由,若需更具体说明可继续修改。"
),
)
if reason_value:
return self._build_slot_value(
value=reason_value,
@@ -3199,19 +3216,6 @@ class UserAgentService:
confidence=0.76,
evidence="系统从用户原始描述中提取了本次费用事由,建议继续核对。",
)
inferred_reason = self._infer_reason_from_claim_groups(
claim_groups=claim_groups,
)
if inferred_reason:
return self._build_slot_value(
value=inferred_reason,
raw_value=inferred_reason,
normalized_value=inferred_reason,
source="ocr",
confidence=0.68,
evidence="系统已根据票据识别场景补全通用事由,若需更具体说明可继续修改。",
)
return self._build_slot_value()
def _build_amount_slot(
@@ -3358,17 +3362,17 @@ class UserAgentService:
return self._build_slot_value()
@staticmethod
def _normalize_amount_text(value: str) -> str:
cleaned = str(value or "").strip()
if not cleaned:
return ""
for alias, canonical in sorted(AMOUNT_UNIT_ALIASES.items(), key=lambda item: len(item[0]), reverse=True):
cleaned = cleaned.replace(alias, canonical)
match = AMOUNT_TEXT_PATTERN.search(cleaned)
if not match:
return cleaned
number = float(match.group(1))
return f"{number:.2f}"
def _normalize_amount_text(value: str) -> str:
cleaned = str(value or "").strip()
if not cleaned:
return ""
for alias, canonical in sorted(AMOUNT_UNIT_ALIASES.items(), key=lambda item: len(item[0]), reverse=True):
cleaned = cleaned.replace(alias, canonical)
match = AMOUNT_TEXT_PATTERN.search(cleaned)
if not match:
return cleaned
number = float(match.group(1))
return f"{number:.2f}"
@staticmethod
def _normalize_expense_type_input(value: str) -> tuple[str, str]:

View File

@@ -1,139 +1,139 @@
README.md
pyproject.toml
src/app/__init__.py
src/app/main.py
src/app/api/__init__.py
src/app/api/deps.py
src/app/api/router.py
src/app/api/v1/__init__.py
src/app/api/v1/router.py
src/app/api/v1/endpoints/__init__.py
src/app/api/v1/endpoints/agent_assets.py
src/app/api/v1/endpoints/agent_runs.py
src/app/api/v1/endpoints/audit_logs.py
src/app/api/v1/endpoints/auth.py
src/app/api/v1/endpoints/bootstrap.py
src/app/api/v1/endpoints/employees.py
src/app/api/v1/endpoints/health.py
src/app/api/v1/endpoints/knowledge.py
src/app/api/v1/endpoints/ocr.py
src/app/api/v1/endpoints/ontology.py
src/app/api/v1/endpoints/orchestrator.py
src/app/api/v1/endpoints/reimbursements.py
src/app/api/v1/endpoints/settings.py
src/app/api/v1/endpoints/system_logs.py
src/app/core/__init__.py
src/app/core/admin_secret.py
src/app/core/agent_enums.py
src/app/core/bootstrap.py
src/app/core/config.py
src/app/core/logging.py
src/app/core/openapi.py
src/app/core/secret_box.py
src/app/core/security.py
src/app/db/__init__.py
src/app/db/base.py
src/app/db/base_class.py
src/app/db/session.py
src/app/middleware/__init__.py
src/app/middleware/logging.py
src/app/models/__init__.py
src/app/models/agent_asset.py
src/app/models/agent_conversation.py
src/app/models/agent_run.py
src/app/models/approval.py
src/app/models/audit_log.py
src/app/models/employee.py
src/app/models/employee_change_log.py
src/app/models/financial_record.py
src/app/models/organization.py
src/app/models/reimbursement.py
src/app/models/role.py
src/app/models/system_model_setting.py
src/app/models/system_setting.py
src/app/models/system_setting_secret.py
src/app/repositories/__init__.py
src/app/repositories/agent_asset.py
src/app/repositories/agent_run.py
src/app/repositories/audit_log.py
src/app/repositories/employee.py
src/app/repositories/reimbursement.py
src/app/repositories/settings.py
src/app/schemas/__init__.py
src/app/schemas/agent_asset.py
src/app/schemas/agent_run.py
src/app/schemas/audit_log.py
src/app/schemas/auth.py
src/app/schemas/bootstrap.py
src/app/schemas/common.py
src/app/schemas/employee.py
src/app/schemas/knowledge.py
src/app/schemas/ocr.py
src/app/schemas/ontology.py
src/app/schemas/orchestrator.py
src/app/schemas/reimbursement.py
src/app/schemas/settings.py
src/app/schemas/system_log.py
src/app/schemas/user_agent.py
src/app/services/__init__.py
src/app/services/agent_asset_spreadsheet.py
src/app/services/agent_assets.py
src/app/services/agent_conversations.py
src/app/services/agent_foundation.py
src/app/services/agent_runs.py
src/app/services/audit.py
src/app/services/auth.py
src/app/services/document_intelligence.py
src/app/services/employee.py
src/app/services/employee_seed.py
src/app/services/expense_claims.py
src/app/services/expense_rule_runtime.py
src/app/services/hermes_sync.py
src/app/services/knowledge.py
src/app/services/knowledge_index_tasks.py
src/app/services/knowledge_normalizer.py
src/app/services/knowledge_rag.py
src/app/services/knowledge_scheduler.py
src/app/services/knowledge_sync.py
src/app/services/model_connectivity.py
src/app/services/ocr.py
src/app/services/ontology.py
src/app/services/orchestrator.py
src/app/services/reimbursement.py
src/app/services/runtime_chat.py
src/app/services/settings.py
src/app/services/system_hermes.py
src/app/services/system_logs.py
src/app/services/user_agent.py
src/x_financial_server.egg-info/PKG-INFO
src/x_financial_server.egg-info/SOURCES.txt
src/x_financial_server.egg-info/dependency_links.txt
src/x_financial_server.egg-info/requires.txt
src/x_financial_server.egg-info/top_level.txt
tests/test_agent_asset_onlyoffice_key.py
tests/test_agent_asset_service.py
tests/test_agent_asset_spreadsheet_import.py
tests/test_agent_foundation_endpoints.py
tests/test_agent_runs_service.py
tests/test_auth_service.py
tests/test_config_settings_reload.py
tests/test_document_intelligence.py
tests/test_employee_service.py
tests/test_env_file_precedence.py
tests/test_expense_claim_service.py
tests/test_imports.py
tests/test_knowledge_normalizer.py
tests/test_knowledge_onlyoffice_config.py
tests/test_knowledge_rag_service.py
tests/test_knowledge_service.py
tests/test_ocr_endpoints.py
tests/test_ocr_service.py
tests/test_ontology_service.py
tests/test_openapi_schema.py
tests/test_reimbursement_endpoints.py
tests/test_runtime_chat_service.py
tests/test_server_start_dependencies.py
tests/test_settings_persistence.py
tests/test_settings_service.py
tests/test_system_logs_service.py
README.md
pyproject.toml
src/app/__init__.py
src/app/main.py
src/app/api/__init__.py
src/app/api/deps.py
src/app/api/router.py
src/app/api/v1/__init__.py
src/app/api/v1/router.py
src/app/api/v1/endpoints/__init__.py
src/app/api/v1/endpoints/agent_assets.py
src/app/api/v1/endpoints/agent_runs.py
src/app/api/v1/endpoints/audit_logs.py
src/app/api/v1/endpoints/auth.py
src/app/api/v1/endpoints/bootstrap.py
src/app/api/v1/endpoints/employees.py
src/app/api/v1/endpoints/health.py
src/app/api/v1/endpoints/knowledge.py
src/app/api/v1/endpoints/ocr.py
src/app/api/v1/endpoints/ontology.py
src/app/api/v1/endpoints/orchestrator.py
src/app/api/v1/endpoints/reimbursements.py
src/app/api/v1/endpoints/settings.py
src/app/api/v1/endpoints/system_logs.py
src/app/core/__init__.py
src/app/core/admin_secret.py
src/app/core/agent_enums.py
src/app/core/bootstrap.py
src/app/core/config.py
src/app/core/logging.py
src/app/core/openapi.py
src/app/core/secret_box.py
src/app/core/security.py
src/app/db/__init__.py
src/app/db/base.py
src/app/db/base_class.py
src/app/db/session.py
src/app/middleware/__init__.py
src/app/middleware/logging.py
src/app/models/__init__.py
src/app/models/agent_asset.py
src/app/models/agent_conversation.py
src/app/models/agent_run.py
src/app/models/approval.py
src/app/models/audit_log.py
src/app/models/employee.py
src/app/models/employee_change_log.py
src/app/models/financial_record.py
src/app/models/organization.py
src/app/models/reimbursement.py
src/app/models/role.py
src/app/models/system_model_setting.py
src/app/models/system_setting.py
src/app/models/system_setting_secret.py
src/app/repositories/__init__.py
src/app/repositories/agent_asset.py
src/app/repositories/agent_run.py
src/app/repositories/audit_log.py
src/app/repositories/employee.py
src/app/repositories/reimbursement.py
src/app/repositories/settings.py
src/app/schemas/__init__.py
src/app/schemas/agent_asset.py
src/app/schemas/agent_run.py
src/app/schemas/audit_log.py
src/app/schemas/auth.py
src/app/schemas/bootstrap.py
src/app/schemas/common.py
src/app/schemas/employee.py
src/app/schemas/knowledge.py
src/app/schemas/ocr.py
src/app/schemas/ontology.py
src/app/schemas/orchestrator.py
src/app/schemas/reimbursement.py
src/app/schemas/settings.py
src/app/schemas/system_log.py
src/app/schemas/user_agent.py
src/app/services/__init__.py
src/app/services/agent_asset_spreadsheet.py
src/app/services/agent_assets.py
src/app/services/agent_conversations.py
src/app/services/agent_foundation.py
src/app/services/agent_runs.py
src/app/services/audit.py
src/app/services/auth.py
src/app/services/document_intelligence.py
src/app/services/employee.py
src/app/services/employee_seed.py
src/app/services/expense_claims.py
src/app/services/expense_rule_runtime.py
src/app/services/hermes_sync.py
src/app/services/knowledge.py
src/app/services/knowledge_index_tasks.py
src/app/services/knowledge_normalizer.py
src/app/services/knowledge_rag.py
src/app/services/knowledge_scheduler.py
src/app/services/knowledge_sync.py
src/app/services/model_connectivity.py
src/app/services/ocr.py
src/app/services/ontology.py
src/app/services/orchestrator.py
src/app/services/reimbursement.py
src/app/services/runtime_chat.py
src/app/services/settings.py
src/app/services/system_hermes.py
src/app/services/system_logs.py
src/app/services/user_agent.py
src/x_financial_server.egg-info/PKG-INFO
src/x_financial_server.egg-info/SOURCES.txt
src/x_financial_server.egg-info/dependency_links.txt
src/x_financial_server.egg-info/requires.txt
src/x_financial_server.egg-info/top_level.txt
tests/test_agent_asset_onlyoffice_key.py
tests/test_agent_asset_service.py
tests/test_agent_asset_spreadsheet_import.py
tests/test_agent_foundation_endpoints.py
tests/test_agent_runs_service.py
tests/test_auth_service.py
tests/test_config_settings_reload.py
tests/test_document_intelligence.py
tests/test_employee_service.py
tests/test_env_file_precedence.py
tests/test_expense_claim_service.py
tests/test_imports.py
tests/test_knowledge_normalizer.py
tests/test_knowledge_onlyoffice_config.py
tests/test_knowledge_rag_service.py
tests/test_knowledge_service.py
tests/test_ocr_endpoints.py
tests/test_ocr_service.py
tests/test_ontology_service.py
tests/test_openapi_schema.py
tests/test_reimbursement_endpoints.py
tests/test_runtime_chat_service.py
tests/test_server_start_dependencies.py
tests/test_settings_persistence.py
tests/test_settings_service.py
tests/test_system_logs_service.py
tests/test_user_agent_service.py

View File

@@ -1,84 +1,84 @@
{
"file_name": "行程单_2_鄂AX9877.pdf",
"storage_key": "0d3102fe-a458-42cf-b30c-4cffeeb74668/c99de539-23cb-4f1a-a21f-a40bce93d54e/行程单_2_鄂AX9877.pdf",
"media_type": "application/pdf",
"size_bytes": 32459,
"uploaded_at": "2026-05-16T08:41:42.540134+00:00",
"previewable": true,
"preview_kind": "image",
"preview_storage_key": "0d3102fe-a458-42cf-b30c-4cffeeb74668/c99de539-23cb-4f1a-a21f-a40bce93d54e/行程单_2_鄂AX9877.preview.png",
"preview_media_type": "image/png",
"preview_file_name": "行程单_2_鄂AX9877.preview.png",
"analysis": {
"severity": "pass",
"label": "AI提示符合条件",
"headline": "AI提示附件符合基础校验条件",
"summary": "已识别到票据类型和关键字段,且符合当前费用场景的附件要求。",
"points": [
"票据类型:已识别为出租车/网约车票据。",
"附件类型要求:当前费用项目为交通费,已识别为出租车/网约车票据,符合当前交通费场景的附件要求。",
"金额字段:已识别到与当前明细接近的金额 35.53 元。"
],
"suggestion": "建议继续核对报销分类、费用说明和业务场景是否一致。"
},
"document_info": {
"document_type": "taxi_receipt",
"document_type_label": "出租车/网约车票据",
"scene_code": "transport",
"scene_label": "交通票据",
"fields": [
{
"key": "amount",
"label": "金额",
"value": "35.53元"
},
{
"key": "date",
"label": "日期",
"value": "2026-03-04"
},
{
"key": "merchant_name",
"label": "商户",
"value": "全季酒店"
}
]
},
"requirement_check": {
"matches": true,
"current_expense_type": "transport",
"current_expense_type_label": "交通费",
"allowed_scene_labels": [
"交通"
],
"allowed_document_type_labels": [
"停车/通行费票据",
"一般收据/凭证",
"出租车/网约车票据",
"增值税发票"
],
"recognized_scene_code": "transport",
"recognized_scene_label": "交通票据",
"recognized_document_type": "taxi_receipt",
"recognized_document_type_label": "出租车/网约车票据",
"mismatch_severity": "high",
"rule_code": "rule.expense.scene_submission_standard",
"rule_name": "报销场景提交与附件标准",
"message": "当前费用项目为交通费,已识别为出租车/网约车票据,符合当前交通费场景的附件要求。"
},
"ocr_status": "recognized",
"ocr_error": "",
"ocr_text": "高德地图一打车\n行程单\nAMAP ITINERARY\n1申请时间2026-03-04\n【行程时间2026-03-0407:05至2026-03-0407:33\n|行程人手机号18602700270\n1共计1单行程合计35.53元\n序号\n服务商\n车型\n上车时间\n城市\n起点\n终点\n金额\n经济型\n2026-03-04\n1\n滴滴出行\n武汉市\n全季酒店武汉工程大学店\n武汉站\n35.53元\n07:05\n页码1/1",
"ocr_summary": "高德地图一打车行程单AMAP ITINERARY",
"ocr_avg_score": 0.9819406509399414,
"ocr_line_count": 25,
"ocr_classification_source": "rule",
"ocr_classification_confidence": 0.88,
"ocr_classification_evidence": [
"滴滴出行",
"滴滴",
"打车",
"上车"
],
"ocr_warnings": []
{
"file_name": "行程单_2_鄂AX9877.pdf",
"storage_key": "0d3102fe-a458-42cf-b30c-4cffeeb74668/c99de539-23cb-4f1a-a21f-a40bce93d54e/行程单_2_鄂AX9877.pdf",
"media_type": "application/pdf",
"size_bytes": 32459,
"uploaded_at": "2026-05-16T08:41:42.540134+00:00",
"previewable": true,
"preview_kind": "image",
"preview_storage_key": "0d3102fe-a458-42cf-b30c-4cffeeb74668/c99de539-23cb-4f1a-a21f-a40bce93d54e/行程单_2_鄂AX9877.preview.png",
"preview_media_type": "image/png",
"preview_file_name": "行程单_2_鄂AX9877.preview.png",
"analysis": {
"severity": "pass",
"label": "AI提示符合条件",
"headline": "AI提示附件符合基础校验条件",
"summary": "已识别到票据类型和关键字段,且符合当前费用场景的附件要求。",
"points": [
"票据类型:已识别为出租车/网约车票据。",
"附件类型要求:当前费用项目为交通费,已识别为出租车/网约车票据,符合当前交通费场景的附件要求。",
"金额字段:已识别到与当前明细接近的金额 35.53 元。"
],
"suggestion": "建议继续核对报销分类、费用说明和业务场景是否一致。"
},
"document_info": {
"document_type": "taxi_receipt",
"document_type_label": "出租车/网约车票据",
"scene_code": "transport",
"scene_label": "交通票据",
"fields": [
{
"key": "amount",
"label": "金额",
"value": "35.53元"
},
{
"key": "date",
"label": "日期",
"value": "2026-03-04"
},
{
"key": "merchant_name",
"label": "商户",
"value": "全季酒店"
}
]
},
"requirement_check": {
"matches": true,
"current_expense_type": "transport",
"current_expense_type_label": "交通费",
"allowed_scene_labels": [
"交通"
],
"allowed_document_type_labels": [
"停车/通行费票据",
"一般收据/凭证",
"出租车/网约车票据",
"增值税发票"
],
"recognized_scene_code": "transport",
"recognized_scene_label": "交通票据",
"recognized_document_type": "taxi_receipt",
"recognized_document_type_label": "出租车/网约车票据",
"mismatch_severity": "high",
"rule_code": "rule.expense.scene_submission_standard",
"rule_name": "报销场景提交与附件标准",
"message": "当前费用项目为交通费,已识别为出租车/网约车票据,符合当前交通费场景的附件要求。"
},
"ocr_status": "recognized",
"ocr_error": "",
"ocr_text": "高德地图一打车\n行程单\nAMAP ITINERARY\n1申请时间2026-03-04\n【行程时间2026-03-0407:05至2026-03-0407:33\n|行程人手机号18602700270\n1共计1单行程合计35.53元\n序号\n服务商\n车型\n上车时间\n城市\n起点\n终点\n金额\n经济型\n2026-03-04\n1\n滴滴出行\n武汉市\n全季酒店武汉工程大学店\n武汉站\n35.53元\n07:05\n页码1/1",
"ocr_summary": "高德地图一打车行程单AMAP ITINERARY",
"ocr_avg_score": 0.9819406509399414,
"ocr_line_count": 25,
"ocr_classification_source": "rule",
"ocr_classification_confidence": 0.88,
"ocr_classification_evidence": [
"滴滴出行",
"滴滴",
"打车",
"上车"
],
"ocr_warnings": []
}

View File

@@ -1,84 +1,84 @@
{
"file_name": "行程单_1_鄂A1S987.pdf",
"storage_key": "281095c5-d85b-428e-924f-250bdd6e0261/c676b663-8851-4b35-be4e-1ba13d46d35e/行程单_1_鄂A1S987.pdf",
"media_type": "application/pdf",
"size_bytes": 34880,
"uploaded_at": "2026-05-16T08:17:53.656595+00:00",
"previewable": true,
"preview_kind": "image",
"preview_storage_key": "281095c5-d85b-428e-924f-250bdd6e0261/c676b663-8851-4b35-be4e-1ba13d46d35e/行程单_1_鄂A1S987.preview.png",
"preview_media_type": "image/png",
"preview_file_name": "行程单_1_鄂A1S987.preview.png",
"analysis": {
"severity": "pass",
"label": "AI提示符合条件",
"headline": "AI提示附件符合基础校验条件",
"summary": "已识别到票据类型和关键字段,且符合当前费用场景的附件要求。",
"points": [
"票据类型:已识别为出租车/网约车票据。",
"附件类型要求:当前费用项目为交通费,已识别为出租车/网约车票据,符合当前交通费场景的附件要求。",
"金额字段:已识别到与当前明细接近的金额 10.30 元。"
],
"suggestion": "建议继续核对报销分类、费用说明和业务场景是否一致。"
},
"document_info": {
"document_type": "taxi_receipt",
"document_type_label": "出租车/网约车票据",
"scene_code": "transport",
"scene_label": "交通票据",
"fields": [
{
"key": "amount",
"label": "金额",
"value": "10.3元"
},
{
"key": "date",
"label": "日期",
"value": "2026-03-01"
},
{
"key": "merchant_name",
"label": "商户",
"value": "全季酒店"
}
]
},
"requirement_check": {
"matches": true,
"current_expense_type": "transport",
"current_expense_type_label": "交通费",
"allowed_scene_labels": [
"交通"
],
"allowed_document_type_labels": [
"停车/通行费票据",
"一般收据/凭证",
"出租车/网约车票据",
"增值税发票"
],
"recognized_scene_code": "transport",
"recognized_scene_label": "交通票据",
"recognized_document_type": "taxi_receipt",
"recognized_document_type_label": "出租车/网约车票据",
"mismatch_severity": "high",
"rule_code": "rule.expense.scene_submission_standard",
"rule_name": "报销场景提交与附件标准",
"message": "当前费用项目为交通费,已识别为出租车/网约车票据,符合当前交通费场景的附件要求。"
},
"ocr_status": "recognized",
"ocr_error": "",
"ocr_text": "高德地图一打车\n行程单\nAMAP ITINERARY\n1申请时间2026-03-01\n【行程时间2026-03-0113:23至2026-03-0113:40\n行程人手机号18602700270\n|共计1单行程合计10.30元\n序号\n服务商\n车型\n上车时间\n城市\n起点\n终点\n金额\n经济型\n2026-03-01\n1\n滴滴出行\n13:23\n武汉市\n金融港北地铁站\n全季酒店武汉工程大学店\n10.30元\n页码1/1",
"ocr_summary": "高德地图一打车行程单AMAP ITINERARY",
"ocr_avg_score": 0.9844024634361267,
"ocr_line_count": 25,
"ocr_classification_source": "rule",
"ocr_classification_confidence": 0.88,
"ocr_classification_evidence": [
"滴滴出行",
"滴滴",
"打车",
"上车"
],
"ocr_warnings": []
{
"file_name": "行程单_1_鄂A1S987.pdf",
"storage_key": "281095c5-d85b-428e-924f-250bdd6e0261/c676b663-8851-4b35-be4e-1ba13d46d35e/行程单_1_鄂A1S987.pdf",
"media_type": "application/pdf",
"size_bytes": 34880,
"uploaded_at": "2026-05-16T08:17:53.656595+00:00",
"previewable": true,
"preview_kind": "image",
"preview_storage_key": "281095c5-d85b-428e-924f-250bdd6e0261/c676b663-8851-4b35-be4e-1ba13d46d35e/行程单_1_鄂A1S987.preview.png",
"preview_media_type": "image/png",
"preview_file_name": "行程单_1_鄂A1S987.preview.png",
"analysis": {
"severity": "pass",
"label": "AI提示符合条件",
"headline": "AI提示附件符合基础校验条件",
"summary": "已识别到票据类型和关键字段,且符合当前费用场景的附件要求。",
"points": [
"票据类型:已识别为出租车/网约车票据。",
"附件类型要求:当前费用项目为交通费,已识别为出租车/网约车票据,符合当前交通费场景的附件要求。",
"金额字段:已识别到与当前明细接近的金额 10.30 元。"
],
"suggestion": "建议继续核对报销分类、费用说明和业务场景是否一致。"
},
"document_info": {
"document_type": "taxi_receipt",
"document_type_label": "出租车/网约车票据",
"scene_code": "transport",
"scene_label": "交通票据",
"fields": [
{
"key": "amount",
"label": "金额",
"value": "10.3元"
},
{
"key": "date",
"label": "日期",
"value": "2026-03-01"
},
{
"key": "merchant_name",
"label": "商户",
"value": "全季酒店"
}
]
},
"requirement_check": {
"matches": true,
"current_expense_type": "transport",
"current_expense_type_label": "交通费",
"allowed_scene_labels": [
"交通"
],
"allowed_document_type_labels": [
"停车/通行费票据",
"一般收据/凭证",
"出租车/网约车票据",
"增值税发票"
],
"recognized_scene_code": "transport",
"recognized_scene_label": "交通票据",
"recognized_document_type": "taxi_receipt",
"recognized_document_type_label": "出租车/网约车票据",
"mismatch_severity": "high",
"rule_code": "rule.expense.scene_submission_standard",
"rule_name": "报销场景提交与附件标准",
"message": "当前费用项目为交通费,已识别为出租车/网约车票据,符合当前交通费场景的附件要求。"
},
"ocr_status": "recognized",
"ocr_error": "",
"ocr_text": "高德地图一打车\n行程单\nAMAP ITINERARY\n1申请时间2026-03-01\n【行程时间2026-03-0113:23至2026-03-0113:40\n行程人手机号18602700270\n|共计1单行程合计10.30元\n序号\n服务商\n车型\n上车时间\n城市\n起点\n终点\n金额\n经济型\n2026-03-01\n1\n滴滴出行\n13:23\n武汉市\n金融港北地铁站\n全季酒店武汉工程大学店\n10.30元\n页码1/1",
"ocr_summary": "高德地图一打车行程单AMAP ITINERARY",
"ocr_avg_score": 0.9844024634361267,
"ocr_line_count": 25,
"ocr_classification_source": "rule",
"ocr_classification_confidence": 0.88,
"ocr_classification_evidence": [
"滴滴出行",
"滴滴",
"打车",
"上车"
],
"ocr_warnings": []
}

View File

@@ -35,13 +35,13 @@
"updated_at": "2026-05-17T13:00:09.485818+00:00",
"uploaded_by": "admin",
"version_number": 1,
"ingest_status": 1,
"ingest_status_updated_at": "2026-05-17T13:00:09.485818+00:00",
"ingest_status": 4,
"ingest_status_updated_at": "2026-05-19T16:00:57.418443+00:00",
"ingest_completed_at": "",
"ingest_document_name": "",
"ingest_document_updated_at": "",
"ingest_document_sha256": "",
"ingest_agent_run_id": ""
"ingest_agent_run_id": "run_57f2d8727aaa4374"
}
]
}

View File

@@ -24,5 +24,28 @@
"processing_start_time": 1779011842,
"processing_end_time": 1779012093
}
},
"a8f8465df08e455ebe133351721d49f8": {
"status": "failed",
"error_msg": "Embedding func: Worker execution timeout after 60s",
"chunks_count": 6,
"chunks_list": [
"chunk-07de6ea74f60535b689f977295770273",
"chunk-99c6f377dff2b9a37a7214b7b05ea9a8",
"chunk-1746bd83138e85e66a78e0cb9ad79272",
"chunk-ce44e4483e4119265b43eacb72e0326a",
"chunk-2187fa0609874bdda339c9850da45a26",
"chunk-2224d777c0b72d0b2dab622c79096c2c"
],
"content_summary": "# 产品需求文档\n## 文档信息\n| 项目 | 内容 |\n|------|------|\n| 项目名称 |\n无单报销\n|\n| 版本 | V1.0 |\n| 日期 | 2026-05-06 |\n| 状态 | 正式版 |\n---\n## 1. 项目概述\n### 1.1 项目背景\n面向\n大型企业\n从业务人员视角出发解决现有ERP使用体验不佳的问题。\n在ERP的发展历程中“单据化”曾是财务合规的一大进步它确保了每笔支出都有据可查。但不可否认传统的人工填单确实\n也制造了很多\n“枷锁”。在AI时代解...",
"content_length": 9088,
"created_at": "2026-05-19T15:59:57.283110+00:00",
"updated_at": "2026-05-19T16:00:57.323299+00:00",
"file_path": "/app/server/storage/knowledge/报销制度/a8f8465df08e455ebe133351721d49f8__无单需求文档0506.docx",
"track_id": "insert_20260519_155957_88c49850",
"metadata": {
"processing_start_time": 1779206397,
"processing_end_time": 1779206457
}
}
}

File diff suppressed because one or more lines are too long

View File

@@ -1,268 +1,268 @@
{
"2c1cb358f08d44ceb0e4d287133206ec": {
"entity_names": [
"工会委员会",
"Business Original Documents",
"First Approver",
"P8",
"一级部门总经理",
"组织人事部",
"业务原始凭据",
"营销中心",
"保证金",
"投标保证金",
"餐补",
"第十四条业务招待费",
"Chief Engineer",
"业务招待",
"Employee Welfare",
"经济舱",
"2024年4月17日",
"三等舱",
"财务信息化系统",
"分管领导",
"重点支出管理规定",
"备用金借款",
"Financial Review",
"第五章附则",
"Company Leadership",
"第十九条",
"经办人",
"预算内支出",
"Current Account Payment",
"Business Entertainment",
"Tax Control System Details",
"第二十一条",
"成本中心归属",
"岗位支出报销审批权限表",
"工会经费管理办法",
"商旅系统",
"Special Subsidy",
"中国银行外汇折算价",
"因公借款",
"资产采购",
"广告费",
"First-Level Department General Manager",
"正式员工",
"一万元",
"公司员工教育培训管理办法",
"责任原则",
"第二章职责分工",
"预算先行",
"Planning and Finance Department",
"Accommodation Cost Reimbursement",
"Official Vehicle Subsidy",
"第四条归口管理部门主要职责",
"Personal Service Compensation",
"邮递费",
"附表3支出归口管理部门与归口业务范围",
"员工",
"第二条目的",
"Director",
"支出归口管理部门与归口业务范围",
"其他支出(员工)",
"报销标准",
"5000000 Yuan Approval Limit",
"第十一条备用金借款",
"会议费",
"第十七条",
"第七条各级管理人员主要职责",
"50000 Yuan Approval Limit",
"全资子公司",
"涉外业务汇率标准",
"总监",
"第十三条差旅费",
"审批权限表",
"商旅订票规范",
"Final Approval Position",
"报销资格",
"新增报销规定",
"公司支出管理办法",
"Institution General Manager",
"房屋租金",
"Staff Activities",
"分包外包(内部单位)",
"报销申请时限",
"Financial Information System",
"Expenditure Authorization Approval Scope",
"直辖市",
"培训费",
"第十二条市内交通费",
"第十五条",
"终审岗",
"Remote Work Housing",
"Centralized Management department",
"第二十条",
"办公室(党委办公室)",
"Three Flows Consistency Principle",
"审批权限",
"VAT Special Invoice",
"后勤服务部",
"员工支出报销审批权限表",
"公司总裁",
"出差补贴",
"Basic Level Managers",
"预付款项",
"附表1员工支出报销审批权限表",
"经办部门",
"信息管理部",
"通信费",
"第十六条",
"增值税发票",
"财务入账条件",
"Hotel Accommodation Standards",
"审批流转程序",
"Self-Driving Travel Provisions",
"交通费",
"第九条支出报销审批",
"薪酬福利支出分配计划",
"产品规划设计部",
"因公用车补贴",
"Committee Chairpersons",
"Business Division General Manager",
"组织安排",
"1 Yuan Per Person Per Kilometer Reimbursement",
"Separation of Approval and Processing Principle",
"第五条计划财务部主要职责",
"200000 Yuan Approval Limit",
"公司各部门",
"第十四条",
"Other Areas",
"分支机构",
"Departments And Units",
"计划财务部",
"Other Employees",
"第二十三条",
"公司团建管理办法",
"火车硬席",
"税控系统明细清单",
"Trade Union Fund",
"报销标准变化情况",
"薪酬福利支出",
"Hong Kong, Macau, And Taiwan Region",
"对外捐赠支出",
"Multi-Level Approval Rule",
"Three Working Days Deadline",
"Employee Remuneration",
"销售退款",
"股权投资、兼并收购",
"控股子公司",
"取消报销规定",
"Procurement Management Regulations",
"Middle Managers",
"差旅费",
"批办分离",
"住宿费",
"Travel Allowance Standards",
"第二十三条本办法的归口与实施",
"Senior Vice President",
"供应商",
"人事归口管理部门",
"Management Personnel At All Levels",
"效益优先",
"Operating Department Individual",
"Remote Work Housing Rental Expenses",
"取消报销规定内容",
"Company",
"修订说明",
"国网数科公司",
"Vice President",
"分级授权",
"Expenditure Reimbursement Application",
"第二十四条附件",
"第二十二条",
"出租车",
"Night High-Speed Rail Provision",
"各级管理人员",
"受益原则",
"公司员工因公通讯费用实施细则",
"公司支出管理办法(2024)",
"出差补贴标准",
"Bid Security Deposit Approval Limits Table",
"第二条范围",
"Company Property Rental Management",
"调动工作",
"远光软件股份有限公司",
"市内交通费",
"交通工具等级标准",
"Operator",
"第八条支出报销申请",
"Directly-Controlled Municipalities And Special Administrative Regions",
"出差规定",
"业务招待费",
"Senior Managers",
"逐级审批规则",
"Company Business Travel System",
"广告宣传费",
"Transportation Cost Reimbursement",
"财务",
"第一章总则",
"材料采购",
"人力资源服务部",
"证券与法律事务部",
"Transportation Level Standards",
"归口管理部门",
"商旅客服",
"第四章重点支出管理规定",
"出差审批程序",
"Business Trip Approval",
"西藏",
"附表2岗位支出报销审批权限表",
"第十八条",
"第二十四条",
"Company Hotel Accommodation Limit Standards",
"办法",
"DAP研发中心",
"新增规定内容",
"基本补助",
"Travel Allowance",
"异地挂职锻炼补贴标准",
"部门负责人",
"Provincial Capitals",
"特区",
"Transportation Tickets",
"第三章支出报销申请与审批",
"品牌及市场运营中心",
"分包外包(外部单位)",
"探亲路费",
"President",
"凭据报销",
"基本出差补贴",
"Taxi Usage Regulations",
"Government Fees",
"Commercial Travel System",
"远光制度202414号",
"审批权限变化情况",
"基建工程",
"支出报销申请与审批",
"中国外汇交易中心参考汇率",
"Department Manager",
"支出报销审批",
"预算调整决策程序",
"公司1号文",
"External Conference Accommodation",
"厉行节约",
"Commercial Insurance",
"公司",
"第三条管理原则",
"捐赠申请",
"分类控制",
"业务宣传费",
"产业投资部",
"公司员工探亲管理办法",
"Subsequent Approver",
"100000 Yuan Approval Limit",
"Tax Authority Recognized Invoice",
"国家电网公司",
"业务佐证材料",
"第六条经办部门(个人)主要职责",
"结算起点",
"第十条支出成本中心归属",
"母公司"
],
"count": 258,
"create_time": 1779012093,
"update_time": 1779012093,
"_id": "2c1cb358f08d44ceb0e4d287133206ec"
}
{
"2c1cb358f08d44ceb0e4d287133206ec": {
"entity_names": [
"工会委员会",
"Business Original Documents",
"First Approver",
"P8",
"一级部门总经理",
"组织人事部",
"业务原始凭据",
"营销中心",
"保证金",
"投标保证金",
"餐补",
"第十四条业务招待费",
"Chief Engineer",
"业务招待",
"Employee Welfare",
"经济舱",
"2024年4月17日",
"三等舱",
"财务信息化系统",
"分管领导",
"重点支出管理规定",
"备用金借款",
"Financial Review",
"第五章附则",
"Company Leadership",
"第十九条",
"经办人",
"预算内支出",
"Current Account Payment",
"Business Entertainment",
"Tax Control System Details",
"第二十一条",
"成本中心归属",
"岗位支出报销审批权限表",
"工会经费管理办法",
"商旅系统",
"Special Subsidy",
"中国银行外汇折算价",
"因公借款",
"资产采购",
"广告费",
"First-Level Department General Manager",
"正式员工",
"一万元",
"公司员工教育培训管理办法",
"责任原则",
"第二章职责分工",
"预算先行",
"Planning and Finance Department",
"Accommodation Cost Reimbursement",
"Official Vehicle Subsidy",
"第四条归口管理部门主要职责",
"Personal Service Compensation",
"邮递费",
"附表3支出归口管理部门与归口业务范围",
"员工",
"第二条目的",
"Director",
"支出归口管理部门与归口业务范围",
"其他支出(员工)",
"报销标准",
"5000000 Yuan Approval Limit",
"第十一条备用金借款",
"会议费",
"第十七条",
"第七条各级管理人员主要职责",
"50000 Yuan Approval Limit",
"全资子公司",
"涉外业务汇率标准",
"总监",
"第十三条差旅费",
"审批权限表",
"商旅订票规范",
"Final Approval Position",
"报销资格",
"新增报销规定",
"公司支出管理办法",
"Institution General Manager",
"房屋租金",
"Staff Activities",
"分包外包(内部单位)",
"报销申请时限",
"Financial Information System",
"Expenditure Authorization Approval Scope",
"直辖市",
"培训费",
"第十二条市内交通费",
"第十五条",
"终审岗",
"Remote Work Housing",
"Centralized Management department",
"第二十条",
"办公室(党委办公室)",
"Three Flows Consistency Principle",
"审批权限",
"VAT Special Invoice",
"后勤服务部",
"员工支出报销审批权限表",
"公司总裁",
"出差补贴",
"Basic Level Managers",
"预付款项",
"附表1员工支出报销审批权限表",
"经办部门",
"信息管理部",
"通信费",
"第十六条",
"增值税发票",
"财务入账条件",
"Hotel Accommodation Standards",
"审批流转程序",
"Self-Driving Travel Provisions",
"交通费",
"第九条支出报销审批",
"薪酬福利支出分配计划",
"产品规划设计部",
"因公用车补贴",
"Committee Chairpersons",
"Business Division General Manager",
"组织安排",
"1 Yuan Per Person Per Kilometer Reimbursement",
"Separation of Approval and Processing Principle",
"第五条计划财务部主要职责",
"200000 Yuan Approval Limit",
"公司各部门",
"第十四条",
"Other Areas",
"分支机构",
"Departments And Units",
"计划财务部",
"Other Employees",
"第二十三条",
"公司团建管理办法",
"火车硬席",
"税控系统明细清单",
"Trade Union Fund",
"报销标准变化情况",
"薪酬福利支出",
"Hong Kong, Macau, And Taiwan Region",
"对外捐赠支出",
"Multi-Level Approval Rule",
"Three Working Days Deadline",
"Employee Remuneration",
"销售退款",
"股权投资、兼并收购",
"控股子公司",
"取消报销规定",
"Procurement Management Regulations",
"Middle Managers",
"差旅费",
"批办分离",
"住宿费",
"Travel Allowance Standards",
"第二十三条本办法的归口与实施",
"Senior Vice President",
"供应商",
"人事归口管理部门",
"Management Personnel At All Levels",
"效益优先",
"Operating Department Individual",
"Remote Work Housing Rental Expenses",
"取消报销规定内容",
"Company",
"修订说明",
"国网数科公司",
"Vice President",
"分级授权",
"Expenditure Reimbursement Application",
"第二十四条附件",
"第二十二条",
"出租车",
"Night High-Speed Rail Provision",
"各级管理人员",
"受益原则",
"公司员工因公通讯费用实施细则",
"公司支出管理办法(2024)",
"出差补贴标准",
"Bid Security Deposit Approval Limits Table",
"第二条范围",
"Company Property Rental Management",
"调动工作",
"远光软件股份有限公司",
"市内交通费",
"交通工具等级标准",
"Operator",
"第八条支出报销申请",
"Directly-Controlled Municipalities And Special Administrative Regions",
"出差规定",
"业务招待费",
"Senior Managers",
"逐级审批规则",
"Company Business Travel System",
"广告宣传费",
"Transportation Cost Reimbursement",
"财务",
"第一章总则",
"材料采购",
"人力资源服务部",
"证券与法律事务部",
"Transportation Level Standards",
"归口管理部门",
"商旅客服",
"第四章重点支出管理规定",
"出差审批程序",
"Business Trip Approval",
"西藏",
"附表2岗位支出报销审批权限表",
"第十八条",
"第二十四条",
"Company Hotel Accommodation Limit Standards",
"办法",
"DAP研发中心",
"新增规定内容",
"基本补助",
"Travel Allowance",
"异地挂职锻炼补贴标准",
"部门负责人",
"Provincial Capitals",
"特区",
"Transportation Tickets",
"第三章支出报销申请与审批",
"品牌及市场运营中心",
"分包外包(外部单位)",
"探亲路费",
"President",
"凭据报销",
"基本出差补贴",
"Taxi Usage Regulations",
"Government Fees",
"Commercial Travel System",
"远光制度202414号",
"审批权限变化情况",
"基建工程",
"支出报销申请与审批",
"中国外汇交易中心参考汇率",
"Department Manager",
"支出报销审批",
"预算调整决策程序",
"公司1号文",
"External Conference Accommodation",
"厉行节约",
"Commercial Insurance",
"公司",
"第三条管理原则",
"捐赠申请",
"分类控制",
"业务宣传费",
"产业投资部",
"公司员工探亲管理办法",
"Subsequent Approver",
"100000 Yuan Approval Limit",
"Tax Authority Recognized Invoice",
"国家电网公司",
"业务佐证材料",
"第六条经办部门(个人)主要职责",
"结算起点",
"第十条支出成本中心归属",
"母公司"
],
"count": 258,
"create_time": 1779012093,
"update_time": 1779012093,
"_id": "2c1cb358f08d44ceb0e4d287133206ec"
}
}

View File

@@ -1,166 +1,166 @@
{
"2c1cb358f08d44ceb0e4d287133206ec": {
"relation_pairs": [
[
"Departments And Units",
"Taxi Usage Regulations"
],
[
"取消报销规定内容",
"报销标准变化情况"
],
[
"业务招待费",
"第十四条"
],
[
"控股子公司",
"计划财务部"
],
[
"公司支出管理办法",
"工会委员会"
],
[
"第一章总则",
"第三条管理原则"
],
[
"广告宣传费",
"第十六条"
],
[
"Tax Control System Details",
"VAT Special Invoice"
],
[
"Expenditure Reimbursement Application",
"Tax Authority Recognized Invoice"
],
[
"远光制度202414号",
"远光软件股份有限公司"
],
[
"Financial Review",
"Operator"
],
[
"Operating Department Individual",
"Procurement Management Regulations"
],
[
"会议费",
"第十五条"
],
[
"Company",
"Management Personnel At All Levels"
],
[
"公司",
"第十七条"
],
[
"公司",
"第十八条"
],
[
"Operator",
"Three Working Days Deadline"
],
[
"第十一条备用金借款",
"第四章重点支出管理规定"
],
[
"Expenditure Reimbursement Application",
"Operator"
],
[
"业务招待费",
"差旅费"
],
[
"公司",
"第二十一条"
],
[
"公司支出管理办法(2024)",
"远光软件股份有限公司"
],
[
"第四条归口管理部门主要职责",
"计划财务部"
],
[
"会议费",
"差旅费"
],
[
"Company",
"Operating Department Individual"
],
[
"商旅系统",
"差旅费"
],
[
"会议费",
"公司总裁"
],
[
"计划财务部",
"远光软件股份有限公司"
],
[
"公司",
"第十九条"
],
[
"公司",
"第二十条"
],
[
"Company",
"Planning and Finance Department"
],
[
"公司支出管理办法",
"营销中心"
],
[
"Business Original Documents",
"Operator"
],
[
"公司支出管理办法",
"办公室(党委办公室)"
],
[
"Departments And Units",
"Night High-Speed Rail Provision"
],
[
"Centralized Management department",
"Company"
],
[
"组织人事部",
"调动工作"
],
[
"报销标准变化情况",
"远光软件股份有限公司"
],
[
"第一章总则",
"远光软件股份有限公司"
]
],
"count": 39,
"create_time": 1779012093,
"update_time": 1779012093,
"_id": "2c1cb358f08d44ceb0e4d287133206ec"
}
{
"2c1cb358f08d44ceb0e4d287133206ec": {
"relation_pairs": [
[
"Departments And Units",
"Taxi Usage Regulations"
],
[
"取消报销规定内容",
"报销标准变化情况"
],
[
"业务招待费",
"第十四条"
],
[
"控股子公司",
"计划财务部"
],
[
"公司支出管理办法",
"工会委员会"
],
[
"第一章总则",
"第三条管理原则"
],
[
"广告宣传费",
"第十六条"
],
[
"Tax Control System Details",
"VAT Special Invoice"
],
[
"Expenditure Reimbursement Application",
"Tax Authority Recognized Invoice"
],
[
"远光制度202414号",
"远光软件股份有限公司"
],
[
"Financial Review",
"Operator"
],
[
"Operating Department Individual",
"Procurement Management Regulations"
],
[
"会议费",
"第十五条"
],
[
"Company",
"Management Personnel At All Levels"
],
[
"公司",
"第十七条"
],
[
"公司",
"第十八条"
],
[
"Operator",
"Three Working Days Deadline"
],
[
"第十一条备用金借款",
"第四章重点支出管理规定"
],
[
"Expenditure Reimbursement Application",
"Operator"
],
[
"业务招待费",
"差旅费"
],
[
"公司",
"第二十一条"
],
[
"公司支出管理办法(2024)",
"远光软件股份有限公司"
],
[
"第四条归口管理部门主要职责",
"计划财务部"
],
[
"会议费",
"差旅费"
],
[
"Company",
"Operating Department Individual"
],
[
"商旅系统",
"差旅费"
],
[
"会议费",
"公司总裁"
],
[
"计划财务部",
"远光软件股份有限公司"
],
[
"公司",
"第十九条"
],
[
"公司",
"第二十条"
],
[
"Company",
"Planning and Finance Department"
],
[
"公司支出管理办法",
"营销中心"
],
[
"Business Original Documents",
"Operator"
],
[
"公司支出管理办法",
"办公室(党委办公室)"
],
[
"Departments And Units",
"Night High-Speed Rail Provision"
],
[
"Centralized Management department",
"Company"
],
[
"组织人事部",
"调动工作"
],
[
"报销标准变化情况",
"远光软件股份有限公司"
],
[
"第一章总则",
"远光软件股份有限公司"
]
],
"count": 39,
"create_time": 1779012093,
"update_time": 1779012093,
"_id": "2c1cb358f08d44ceb0e4d287133206ec"
}
}

View File

@@ -1,353 +1,353 @@
{
"第一章总则<SEP>远光软件股份有限公司": {
"chunk_ids": [
"chunk-aa5435156b829944c173fa1d2d7a93d4"
],
"count": 1,
"create_time": 1779012088,
"update_time": 1779012088,
"_id": "第一章总则<SEP>远光软件股份有限公司"
},
"第十一条备用金借款<SEP>第四章重点支出管理规定": {
"chunk_ids": [
"chunk-aa5435156b829944c173fa1d2d7a93d4"
],
"count": 1,
"create_time": 1779012088,
"update_time": 1779012088,
"_id": "第十一条备用金借款<SEP>第四章重点支出管理规定"
},
"公司支出管理办法<SEP>办公室(党委办公室)": {
"chunk_ids": [
"chunk-afc57a0e9548d1f484da6df6c182676b"
],
"count": 1,
"create_time": 1779012088,
"update_time": 1779012088,
"_id": "公司支出管理办法<SEP>办公室(党委办公室)"
},
"计划财务部<SEP>远光软件股份有限公司": {
"chunk_ids": [
"chunk-aa5435156b829944c173fa1d2d7a93d4"
],
"count": 1,
"create_time": 1779012076,
"update_time": 1779012076,
"_id": "计划财务部<SEP>远光软件股份有限公司"
},
"第一章总则<SEP>第三条管理原则": {
"chunk_ids": [
"chunk-aa5435156b829944c173fa1d2d7a93d4"
],
"count": 1,
"create_time": 1779012076,
"update_time": 1779012076,
"_id": "第一章总则<SEP>第三条管理原则"
},
"Company<SEP>Management Personnel At All Levels": {
"chunk_ids": [
"chunk-74c01decac4a10cd40a491786743b0ee"
],
"count": 1,
"create_time": 1779012076,
"update_time": 1779012076,
"_id": "Company<SEP>Management Personnel At All Levels"
},
"Centralized Management department<SEP>Company": {
"chunk_ids": [
"chunk-74c01decac4a10cd40a491786743b0ee"
],
"count": 1,
"create_time": 1779012077,
"update_time": 1779012077,
"_id": "Centralized Management department<SEP>Company"
},
"Company<SEP>Planning and Finance Department": {
"chunk_ids": [
"chunk-74c01decac4a10cd40a491786743b0ee"
],
"count": 1,
"create_time": 1779012077,
"update_time": 1779012077,
"_id": "Company<SEP>Planning and Finance Department"
},
"Company<SEP>Operating Department Individual": {
"chunk_ids": [
"chunk-74c01decac4a10cd40a491786743b0ee"
],
"count": 1,
"create_time": 1779012078,
"update_time": 1779012078,
"_id": "Company<SEP>Operating Department Individual"
},
"公司支出管理办法<SEP>工会委员会": {
"chunk_ids": [
"chunk-afc57a0e9548d1f484da6df6c182676b"
],
"count": 1,
"create_time": 1779012079,
"update_time": 1779012079,
"_id": "公司支出管理办法<SEP>工会委员会"
},
"Expenditure Reimbursement Application<SEP>Operator": {
"chunk_ids": [
"chunk-74c01decac4a10cd40a491786743b0ee"
],
"count": 1,
"create_time": 1779012079,
"update_time": 1779012079,
"_id": "Expenditure Reimbursement Application<SEP>Operator"
},
"公司支出管理办法<SEP>营销中心": {
"chunk_ids": [
"chunk-afc57a0e9548d1f484da6df6c182676b"
],
"count": 1,
"create_time": 1779012079,
"update_time": 1779012079,
"_id": "公司支出管理办法<SEP>营销中心"
},
"第四条归口管理部门主要职责<SEP>计划财务部": {
"chunk_ids": [
"chunk-aa5435156b829944c173fa1d2d7a93d4"
],
"count": 1,
"create_time": 1779012079,
"update_time": 1779012079,
"_id": "第四条归口管理部门主要职责<SEP>计划财务部"
},
"Tax Control System Details<SEP>VAT Special Invoice": {
"chunk_ids": [
"chunk-74c01decac4a10cd40a491786743b0ee"
],
"count": 1,
"create_time": 1779012079,
"update_time": 1779012079,
"_id": "Tax Control System Details<SEP>VAT Special Invoice"
},
"Operating Department Individual<SEP>Procurement Management Regulations": {
"chunk_ids": [
"chunk-74c01decac4a10cd40a491786743b0ee"
],
"count": 1,
"create_time": 1779012081,
"update_time": 1779012081,
"_id": "Operating Department Individual<SEP>Procurement Management Regulations"
},
"Business Original Documents<SEP>Operator": {
"chunk_ids": [
"chunk-74c01decac4a10cd40a491786743b0ee"
],
"count": 1,
"create_time": 1779012094,
"update_time": 1779012094,
"_id": "Business Original Documents<SEP>Operator"
},
"Expenditure Reimbursement Application<SEP>Tax Authority Recognized Invoice": {
"chunk_ids": [
"chunk-74c01decac4a10cd40a491786743b0ee"
],
"count": 1,
"create_time": 1779012094,
"update_time": 1779012094,
"_id": "Expenditure Reimbursement Application<SEP>Tax Authority Recognized Invoice"
},
"公司<SEP>第十七条": {
"chunk_ids": [
"chunk-e9438f69c9e221d9f0f00a05ad84eac6"
],
"count": 1,
"create_time": 1779012094,
"update_time": 1779012094,
"_id": "公司<SEP>第十七条"
},
"Operator<SEP>Three Working Days Deadline": {
"chunk_ids": [
"chunk-74c01decac4a10cd40a491786743b0ee"
],
"count": 1,
"create_time": 1779012083,
"update_time": 1779012083,
"_id": "Operator<SEP>Three Working Days Deadline"
},
"Departments And Units<SEP>Night High-Speed Rail Provision": {
"chunk_ids": [
"chunk-613d6dfd4c5e9c807229a3147f96b584"
],
"count": 1,
"create_time": 1779012084,
"update_time": 1779012084,
"_id": "Departments And Units<SEP>Night High-Speed Rail Provision"
},
"公司<SEP>第十八条": {
"chunk_ids": [
"chunk-e9438f69c9e221d9f0f00a05ad84eac6"
],
"count": 1,
"create_time": 1779012084,
"update_time": 1779012084,
"_id": "公司<SEP>第十八条"
},
"公司<SEP>第十九条": {
"chunk_ids": [
"chunk-e9438f69c9e221d9f0f00a05ad84eac6"
],
"count": 1,
"create_time": 1779012084,
"update_time": 1779012084,
"_id": "公司<SEP>第十九条"
},
"报销标准变化情况<SEP>远光软件股份有限公司": {
"chunk_ids": [
"chunk-18d968b78afe916b419c1b5973421ebe"
],
"count": 1,
"create_time": 1779012084,
"update_time": 1779012084,
"_id": "报销标准变化情况<SEP>远光软件股份有限公司"
},
"取消报销规定内容<SEP>报销标准变化情况": {
"chunk_ids": [
"chunk-18d968b78afe916b419c1b5973421ebe"
],
"count": 1,
"create_time": 1779012085,
"update_time": 1779012085,
"_id": "取消报销规定内容<SEP>报销标准变化情况"
},
"Financial Review<SEP>Operator": {
"chunk_ids": [
"chunk-74c01decac4a10cd40a491786743b0ee"
],
"count": 1,
"create_time": 1779012085,
"update_time": 1779012085,
"_id": "Financial Review<SEP>Operator"
},
"公司支出管理办法(2024)<SEP>远光软件股份有限公司": {
"chunk_ids": [
"chunk-dd87aa5bc62cc9587ecb4c26d35a5263"
],
"count": 1,
"create_time": 1779012085,
"update_time": 1779012085,
"_id": "公司支出管理办法(2024)<SEP>远光软件股份有限公司"
},
"远光制度202414号<SEP>远光软件股份有限公司": {
"chunk_ids": [
"chunk-dd87aa5bc62cc9587ecb4c26d35a5263"
],
"count": 1,
"create_time": 1779012086,
"update_time": 1779012086,
"_id": "远光制度202414号<SEP>远光软件股份有限公司"
},
"Departments And Units<SEP>Taxi Usage Regulations": {
"chunk_ids": [
"chunk-613d6dfd4c5e9c807229a3147f96b584"
],
"count": 1,
"create_time": 1779012099,
"update_time": 1779012099,
"_id": "Departments And Units<SEP>Taxi Usage Regulations"
},
"控股子公司<SEP>计划财务部": {
"chunk_ids": [
"chunk-dd87aa5bc62cc9587ecb4c26d35a5263"
],
"count": 1,
"create_time": 1779012099,
"update_time": 1779012099,
"_id": "控股子公司<SEP>计划财务部"
},
"公司<SEP>第二十条": {
"chunk_ids": [
"chunk-e9438f69c9e221d9f0f00a05ad84eac6"
],
"count": 1,
"create_time": 1779012086,
"update_time": 1779012086,
"_id": "公司<SEP>第二十条"
},
"商旅系统<SEP>差旅费": {
"chunk_ids": [
"chunk-d26b288ed4001dc5c504dce0eb841362"
],
"count": 1,
"create_time": 1779012086,
"update_time": 1779012086,
"_id": "商旅系统<SEP>差旅费"
},
"业务招待费<SEP>差旅费": {
"chunk_ids": [
"chunk-d26b288ed4001dc5c504dce0eb841362"
],
"count": 1,
"create_time": 1779012089,
"update_time": 1779012089,
"_id": "业务招待费<SEP>差旅费"
},
"公司<SEP>第二十一条": {
"chunk_ids": [
"chunk-e9438f69c9e221d9f0f00a05ad84eac6"
],
"count": 1,
"create_time": 1779012089,
"update_time": 1779012089,
"_id": "公司<SEP>第二十一条"
},
"广告宣传费<SEP>第十六条": {
"chunk_ids": [
"chunk-d26b288ed4001dc5c504dce0eb841362"
],
"count": 1,
"create_time": 1779012089,
"update_time": 1779012089,
"_id": "广告宣传费<SEP>第十六条"
},
"组织人事部<SEP>调动工作": {
"chunk_ids": [
"chunk-d26b288ed4001dc5c504dce0eb841362"
],
"count": 1,
"create_time": 1779012090,
"update_time": 1779012090,
"_id": "组织人事部<SEP>调动工作"
},
"会议费<SEP>差旅费": {
"chunk_ids": [
"chunk-d26b288ed4001dc5c504dce0eb841362"
],
"count": 1,
"create_time": 1779012092,
"update_time": 1779012092,
"_id": "会议费<SEP>差旅费"
},
"业务招待费<SEP>第十四条": {
"chunk_ids": [
"chunk-d26b288ed4001dc5c504dce0eb841362"
],
"count": 1,
"create_time": 1779012092,
"update_time": 1779012092,
"_id": "业务招待费<SEP>第十四条"
},
"会议费<SEP>第十五条": {
"chunk_ids": [
"chunk-d26b288ed4001dc5c504dce0eb841362"
],
"count": 1,
"create_time": 1779012092,
"update_time": 1779012092,
"_id": "会议费<SEP>第十五条"
},
"会议费<SEP>公司总裁": {
"chunk_ids": [
"chunk-d26b288ed4001dc5c504dce0eb841362"
],
"count": 1,
"create_time": 1779012093,
"update_time": 1779012093,
"_id": "会议费<SEP>公司总裁"
}
{
"第一章总则<SEP>远光软件股份有限公司": {
"chunk_ids": [
"chunk-aa5435156b829944c173fa1d2d7a93d4"
],
"count": 1,
"create_time": 1779012088,
"update_time": 1779012088,
"_id": "第一章总则<SEP>远光软件股份有限公司"
},
"第十一条备用金借款<SEP>第四章重点支出管理规定": {
"chunk_ids": [
"chunk-aa5435156b829944c173fa1d2d7a93d4"
],
"count": 1,
"create_time": 1779012088,
"update_time": 1779012088,
"_id": "第十一条备用金借款<SEP>第四章重点支出管理规定"
},
"公司支出管理办法<SEP>办公室(党委办公室)": {
"chunk_ids": [
"chunk-afc57a0e9548d1f484da6df6c182676b"
],
"count": 1,
"create_time": 1779012088,
"update_time": 1779012088,
"_id": "公司支出管理办法<SEP>办公室(党委办公室)"
},
"计划财务部<SEP>远光软件股份有限公司": {
"chunk_ids": [
"chunk-aa5435156b829944c173fa1d2d7a93d4"
],
"count": 1,
"create_time": 1779012076,
"update_time": 1779012076,
"_id": "计划财务部<SEP>远光软件股份有限公司"
},
"第一章总则<SEP>第三条管理原则": {
"chunk_ids": [
"chunk-aa5435156b829944c173fa1d2d7a93d4"
],
"count": 1,
"create_time": 1779012076,
"update_time": 1779012076,
"_id": "第一章总则<SEP>第三条管理原则"
},
"Company<SEP>Management Personnel At All Levels": {
"chunk_ids": [
"chunk-74c01decac4a10cd40a491786743b0ee"
],
"count": 1,
"create_time": 1779012076,
"update_time": 1779012076,
"_id": "Company<SEP>Management Personnel At All Levels"
},
"Centralized Management department<SEP>Company": {
"chunk_ids": [
"chunk-74c01decac4a10cd40a491786743b0ee"
],
"count": 1,
"create_time": 1779012077,
"update_time": 1779012077,
"_id": "Centralized Management department<SEP>Company"
},
"Company<SEP>Planning and Finance Department": {
"chunk_ids": [
"chunk-74c01decac4a10cd40a491786743b0ee"
],
"count": 1,
"create_time": 1779012077,
"update_time": 1779012077,
"_id": "Company<SEP>Planning and Finance Department"
},
"Company<SEP>Operating Department Individual": {
"chunk_ids": [
"chunk-74c01decac4a10cd40a491786743b0ee"
],
"count": 1,
"create_time": 1779012078,
"update_time": 1779012078,
"_id": "Company<SEP>Operating Department Individual"
},
"公司支出管理办法<SEP>工会委员会": {
"chunk_ids": [
"chunk-afc57a0e9548d1f484da6df6c182676b"
],
"count": 1,
"create_time": 1779012079,
"update_time": 1779012079,
"_id": "公司支出管理办法<SEP>工会委员会"
},
"Expenditure Reimbursement Application<SEP>Operator": {
"chunk_ids": [
"chunk-74c01decac4a10cd40a491786743b0ee"
],
"count": 1,
"create_time": 1779012079,
"update_time": 1779012079,
"_id": "Expenditure Reimbursement Application<SEP>Operator"
},
"公司支出管理办法<SEP>营销中心": {
"chunk_ids": [
"chunk-afc57a0e9548d1f484da6df6c182676b"
],
"count": 1,
"create_time": 1779012079,
"update_time": 1779012079,
"_id": "公司支出管理办法<SEP>营销中心"
},
"第四条归口管理部门主要职责<SEP>计划财务部": {
"chunk_ids": [
"chunk-aa5435156b829944c173fa1d2d7a93d4"
],
"count": 1,
"create_time": 1779012079,
"update_time": 1779012079,
"_id": "第四条归口管理部门主要职责<SEP>计划财务部"
},
"Tax Control System Details<SEP>VAT Special Invoice": {
"chunk_ids": [
"chunk-74c01decac4a10cd40a491786743b0ee"
],
"count": 1,
"create_time": 1779012079,
"update_time": 1779012079,
"_id": "Tax Control System Details<SEP>VAT Special Invoice"
},
"Operating Department Individual<SEP>Procurement Management Regulations": {
"chunk_ids": [
"chunk-74c01decac4a10cd40a491786743b0ee"
],
"count": 1,
"create_time": 1779012081,
"update_time": 1779012081,
"_id": "Operating Department Individual<SEP>Procurement Management Regulations"
},
"Business Original Documents<SEP>Operator": {
"chunk_ids": [
"chunk-74c01decac4a10cd40a491786743b0ee"
],
"count": 1,
"create_time": 1779012094,
"update_time": 1779012094,
"_id": "Business Original Documents<SEP>Operator"
},
"Expenditure Reimbursement Application<SEP>Tax Authority Recognized Invoice": {
"chunk_ids": [
"chunk-74c01decac4a10cd40a491786743b0ee"
],
"count": 1,
"create_time": 1779012094,
"update_time": 1779012094,
"_id": "Expenditure Reimbursement Application<SEP>Tax Authority Recognized Invoice"
},
"公司<SEP>第十七条": {
"chunk_ids": [
"chunk-e9438f69c9e221d9f0f00a05ad84eac6"
],
"count": 1,
"create_time": 1779012094,
"update_time": 1779012094,
"_id": "公司<SEP>第十七条"
},
"Operator<SEP>Three Working Days Deadline": {
"chunk_ids": [
"chunk-74c01decac4a10cd40a491786743b0ee"
],
"count": 1,
"create_time": 1779012083,
"update_time": 1779012083,
"_id": "Operator<SEP>Three Working Days Deadline"
},
"Departments And Units<SEP>Night High-Speed Rail Provision": {
"chunk_ids": [
"chunk-613d6dfd4c5e9c807229a3147f96b584"
],
"count": 1,
"create_time": 1779012084,
"update_time": 1779012084,
"_id": "Departments And Units<SEP>Night High-Speed Rail Provision"
},
"公司<SEP>第十八条": {
"chunk_ids": [
"chunk-e9438f69c9e221d9f0f00a05ad84eac6"
],
"count": 1,
"create_time": 1779012084,
"update_time": 1779012084,
"_id": "公司<SEP>第十八条"
},
"公司<SEP>第十九条": {
"chunk_ids": [
"chunk-e9438f69c9e221d9f0f00a05ad84eac6"
],
"count": 1,
"create_time": 1779012084,
"update_time": 1779012084,
"_id": "公司<SEP>第十九条"
},
"报销标准变化情况<SEP>远光软件股份有限公司": {
"chunk_ids": [
"chunk-18d968b78afe916b419c1b5973421ebe"
],
"count": 1,
"create_time": 1779012084,
"update_time": 1779012084,
"_id": "报销标准变化情况<SEP>远光软件股份有限公司"
},
"取消报销规定内容<SEP>报销标准变化情况": {
"chunk_ids": [
"chunk-18d968b78afe916b419c1b5973421ebe"
],
"count": 1,
"create_time": 1779012085,
"update_time": 1779012085,
"_id": "取消报销规定内容<SEP>报销标准变化情况"
},
"Financial Review<SEP>Operator": {
"chunk_ids": [
"chunk-74c01decac4a10cd40a491786743b0ee"
],
"count": 1,
"create_time": 1779012085,
"update_time": 1779012085,
"_id": "Financial Review<SEP>Operator"
},
"公司支出管理办法(2024)<SEP>远光软件股份有限公司": {
"chunk_ids": [
"chunk-dd87aa5bc62cc9587ecb4c26d35a5263"
],
"count": 1,
"create_time": 1779012085,
"update_time": 1779012085,
"_id": "公司支出管理办法(2024)<SEP>远光软件股份有限公司"
},
"远光制度202414号<SEP>远光软件股份有限公司": {
"chunk_ids": [
"chunk-dd87aa5bc62cc9587ecb4c26d35a5263"
],
"count": 1,
"create_time": 1779012086,
"update_time": 1779012086,
"_id": "远光制度202414号<SEP>远光软件股份有限公司"
},
"Departments And Units<SEP>Taxi Usage Regulations": {
"chunk_ids": [
"chunk-613d6dfd4c5e9c807229a3147f96b584"
],
"count": 1,
"create_time": 1779012099,
"update_time": 1779012099,
"_id": "Departments And Units<SEP>Taxi Usage Regulations"
},
"控股子公司<SEP>计划财务部": {
"chunk_ids": [
"chunk-dd87aa5bc62cc9587ecb4c26d35a5263"
],
"count": 1,
"create_time": 1779012099,
"update_time": 1779012099,
"_id": "控股子公司<SEP>计划财务部"
},
"公司<SEP>第二十条": {
"chunk_ids": [
"chunk-e9438f69c9e221d9f0f00a05ad84eac6"
],
"count": 1,
"create_time": 1779012086,
"update_time": 1779012086,
"_id": "公司<SEP>第二十条"
},
"商旅系统<SEP>差旅费": {
"chunk_ids": [
"chunk-d26b288ed4001dc5c504dce0eb841362"
],
"count": 1,
"create_time": 1779012086,
"update_time": 1779012086,
"_id": "商旅系统<SEP>差旅费"
},
"业务招待费<SEP>差旅费": {
"chunk_ids": [
"chunk-d26b288ed4001dc5c504dce0eb841362"
],
"count": 1,
"create_time": 1779012089,
"update_time": 1779012089,
"_id": "业务招待费<SEP>差旅费"
},
"公司<SEP>第二十一条": {
"chunk_ids": [
"chunk-e9438f69c9e221d9f0f00a05ad84eac6"
],
"count": 1,
"create_time": 1779012089,
"update_time": 1779012089,
"_id": "公司<SEP>第二十一条"
},
"广告宣传费<SEP>第十六条": {
"chunk_ids": [
"chunk-d26b288ed4001dc5c504dce0eb841362"
],
"count": 1,
"create_time": 1779012089,
"update_time": 1779012089,
"_id": "广告宣传费<SEP>第十六条"
},
"组织人事部<SEP>调动工作": {
"chunk_ids": [
"chunk-d26b288ed4001dc5c504dce0eb841362"
],
"count": 1,
"create_time": 1779012090,
"update_time": 1779012090,
"_id": "组织人事部<SEP>调动工作"
},
"会议费<SEP>差旅费": {
"chunk_ids": [
"chunk-d26b288ed4001dc5c504dce0eb841362"
],
"count": 1,
"create_time": 1779012092,
"update_time": 1779012092,
"_id": "会议费<SEP>差旅费"
},
"业务招待费<SEP>第十四条": {
"chunk_ids": [
"chunk-d26b288ed4001dc5c504dce0eb841362"
],
"count": 1,
"create_time": 1779012092,
"update_time": 1779012092,
"_id": "业务招待费<SEP>第十四条"
},
"会议费<SEP>第十五条": {
"chunk_ids": [
"chunk-d26b288ed4001dc5c504dce0eb841362"
],
"count": 1,
"create_time": 1779012092,
"update_time": 1779012092,
"_id": "会议费<SEP>第十五条"
},
"会议费<SEP>公司总裁": {
"chunk_ids": [
"chunk-d26b288ed4001dc5c504dce0eb841362"
],
"count": 1,
"create_time": 1779012093,
"update_time": 1779012093,
"_id": "会议费<SEP>公司总裁"
}
}

File diff suppressed because one or more lines are too long

View File

@@ -15,9 +15,8 @@ def test_rule_spreadsheet_onlyoffice_key_uses_safe_characters() -> None:
key = AgentAssetService._build_onlyoffice_document_key(
"asset:id",
"v1.0.0",
metadata,
)
assert key == "asset_id-v1.0.0-abc123"
assert key == "asset_id-abc123"
assert ":" not in key

View File

@@ -310,7 +310,7 @@ def test_restore_version_creates_new_working_copy_without_rewriting_published_ve
assert restored.current_version_change_note == "基于历史版本 v1.0.0 恢复生成工作稿"
def test_spreadsheet_version_compare_returns_sheet_and_cell_changes() -> None:
def test_spreadsheet_upload_records_sheet_and_cell_changes_without_versions() -> None:
with build_session() as db:
service = AgentAssetService(db)
rule = next(
@@ -322,34 +322,33 @@ def test_spreadsheet_version_compare_returns_sheet_and_cell_changes() -> None:
service.upload_rule_spreadsheet(
rule.id,
filename="公司差旅费报销规则.xlsx",
content=build_workbook_bytes([["城市", "住宿"], ["北京", 500]]),
content=build_workbook_bytes([["城市", "住宿"], ["北京", 500]]),
actor="finance_user",
)
base_version = service.get_asset(rule.id).working_version # type: ignore[union-attr]
service.upload_rule_spreadsheet(
rule.id,
filename="公司差旅费报销规则.xlsx",
content=build_workbook_bytes([["城市", "住宿"], ["北京", 550], ["武汉", 450]]),
actor="finance_user",
)
target_version = service.get_asset(rule.id).working_version # type: ignore[union-attr]
diff = service.compare_spreadsheet_versions(
rule.id,
base_version=base_version or "",
target_version=target_version or "",
)
records = service.list_spreadsheet_change_records(rule.id)
latest = records[0]
assert diff.changed_sheet_count == 1
assert diff.changed_cell_count == 3
assert latest.changed_sheet_count == 1
assert latest.changed_cell_count == 3
assert any(
item.cell == "B2" and item.change_type == "modified"
for item in diff.cell_changes
for item in latest.cell_changes
)
assert any(item.cell == "A3" and item.change_type == "added" for item in diff.cell_changes)
assert any(
item.cell == "A3" and item.change_type == "added"
for item in latest.cell_changes
)
assert not hasattr(latest, "version")
def test_working_spreadsheet_version_reads_immutable_snapshot_instead_of_live_copy() -> None:
def test_spreadsheet_content_reads_current_rule_file_without_version_snapshot() -> None:
with build_session() as db:
service = AgentAssetService(db)
rule = next(
@@ -366,7 +365,6 @@ def test_working_spreadsheet_version_reads_immutable_snapshot_instead_of_live_co
)
detail = service.get_asset(rule.id)
assert detail is not None
working_version = detail.working_version or ""
current_asset = service.repository.get(rule.id)
assert current_asset is not None
@@ -375,23 +373,13 @@ def test_working_spreadsheet_version_reads_immutable_snapshot_instead_of_live_co
assert "agent_assets" not in live_storage_key
live_path = service.spreadsheet_manager.resolve_storage_path(live_storage_key)
assert not service.spreadsheet_manager.asset_root.exists()
original_live_bytes = live_path.read_bytes()
try:
live_path.write_bytes(build_workbook_bytes([["城市", "住宿"], ["北京", 999]]))
snapshot_path, _, _ = service.get_rule_spreadsheet_content(
rule.id,
version=working_version,
)
current_path, _, _ = service.get_rule_spreadsheet_content(rule.id)
assert snapshot_path != live_path
assert FINANCE_RULES_LIBRARY in snapshot_path.parts
assert ".versions" in snapshot_path.parts
assert "agent_assets" not in snapshot_path.parts
workbook = load_workbook(snapshot_path, data_only=False)
assert workbook.active["B2"].value == 500
finally:
live_path.write_bytes(original_live_bytes)
assert current_path == live_path
assert ".versions" not in current_path.parts
workbook = load_workbook(current_path, data_only=False)
assert workbook.active["B2"].value == 500
def test_spreadsheet_change_records_return_recent_edit_details() -> None:
@@ -454,7 +442,6 @@ def test_spreadsheet_change_records_include_all_modified_sheets() -> None:
)
detail = service.get_asset(rule.id)
assert detail is not None
first_version = detail.working_version
service.upload_rule_spreadsheet(
rule.id,
@@ -473,7 +460,7 @@ def test_spreadsheet_change_records_include_all_modified_sheets() -> None:
changed_sheets = {item.sheet_name for item in latest.sheet_changes}
changed_cell_sheets = {item.sheet_name for item in latest.cell_changes}
assert latest.version != first_version
assert not hasattr(latest, "version")
assert latest.changed_sheet_count == 2
assert {"差旅标准", "填表说明"}.issubset(changed_sheets)
assert {"差旅标准", "填表说明"}.issubset(changed_cell_sheets)
@@ -513,6 +500,8 @@ def test_editable_spreadsheet_onlyoffice_config_enables_forcesave(monkeypatch) -
customization = config.config["editorConfig"]["customization"]
assert config.config["editorConfig"]["mode"] == "edit"
assert customization["forcesave"] is True
assert "version=" not in config.config["document"]["url"]
assert "version=" not in config.config["editorConfig"]["callbackUrl"]
def test_version_timeline_contains_created_review_and_publish_events() -> None:

View File

@@ -1,98 +1,98 @@
from __future__ import annotations
from collections.abc import Generator
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.core.agent_enums import AgentAssetStatus
from app.db.base import Base
from app.main import create_app
from app.services.agent_assets import AgentAssetService
def build_client() -> tuple[TestClient, sessionmaker[Session]]:
engine = create_engine(
"sqlite+pysqlite:///:memory:",
connect_args={"check_same_thread": False},
poolclass=StaticPool,
)
Base.metadata.create_all(bind=engine)
session_factory = sessionmaker(bind=engine, autoflush=False, autocommit=False)
app = create_app()
def override_db() -> Generator[Session, None, None]:
db = session_factory()
try:
yield db
finally:
db.close()
app.dependency_overrides[get_db] = override_db
return TestClient(app), session_factory
def test_list_agent_assets_endpoint_returns_seeded_items() -> None:
client, _ = build_client()
response = client.get("/api/v1/agent-assets", params={"asset_type": "rule"})
assert response.status_code == 200
payload = response.json()
assert payload
assert all(item["asset_type"] == "rule" for item in payload)
assert any(item["code"] == "rule.expense.travel_risk_control_standard" for item in payload)
def test_get_agent_asset_detail_endpoint_returns_version_history() -> None:
client, _ = build_client()
list_response = client.get("/api/v1/agent-assets", params={"asset_type": "rule"})
asset_id = next(
item["id"]
for item in list_response.json()
if item["code"] == "rule.expense.travel_risk_control_standard"
)
response = client.get(f"/api/v1/agent-assets/{asset_id}")
assert response.status_code == 200
payload = response.json()
assert payload["recent_versions"]
assert payload["current_version_content_type"] == "markdown"
assert payload["current_version"] == "v1.1.0"
assert "行程闭环" in payload["current_version_content"]
def test_activate_pending_rule_endpoint_is_blocked() -> None:
client, session_factory = build_client()
with session_factory() as db:
pending_rule = next(
item
for item in AgentAssetService(db).list_assets(asset_type="rule")
if item.status == AgentAssetStatus.REVIEW.value
)
response = client.post(
f"/api/v1/agent-assets/{pending_rule.id}/activate",
headers={"x-actor": "pytest"},
)
assert response.status_code == 400
assert "审核" in response.json()["detail"]
def test_list_audit_logs_endpoint_returns_seeded_logs() -> None:
client, _ = build_client()
response = client.get("/api/v1/audit-logs")
assert response.status_code == 200
payload = response.json()
assert payload
assert any(item["action"] == "review_rule" for item in payload)
from __future__ import annotations
from collections.abc import Generator
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.core.agent_enums import AgentAssetStatus
from app.db.base import Base
from app.main import create_app
from app.services.agent_assets import AgentAssetService
def build_client() -> tuple[TestClient, sessionmaker[Session]]:
engine = create_engine(
"sqlite+pysqlite:///:memory:",
connect_args={"check_same_thread": False},
poolclass=StaticPool,
)
Base.metadata.create_all(bind=engine)
session_factory = sessionmaker(bind=engine, autoflush=False, autocommit=False)
app = create_app()
def override_db() -> Generator[Session, None, None]:
db = session_factory()
try:
yield db
finally:
db.close()
app.dependency_overrides[get_db] = override_db
return TestClient(app), session_factory
def test_list_agent_assets_endpoint_returns_seeded_items() -> None:
client, _ = build_client()
response = client.get("/api/v1/agent-assets", params={"asset_type": "rule"})
assert response.status_code == 200
payload = response.json()
assert payload
assert all(item["asset_type"] == "rule" for item in payload)
assert any(item["code"] == "rule.expense.travel_risk_control_standard" for item in payload)
def test_get_agent_asset_detail_endpoint_returns_version_history() -> None:
client, _ = build_client()
list_response = client.get("/api/v1/agent-assets", params={"asset_type": "rule"})
asset_id = next(
item["id"]
for item in list_response.json()
if item["code"] == "rule.expense.travel_risk_control_standard"
)
response = client.get(f"/api/v1/agent-assets/{asset_id}")
assert response.status_code == 200
payload = response.json()
assert payload["recent_versions"]
assert payload["current_version_content_type"] == "markdown"
assert payload["current_version"] == "v1.1.0"
assert "行程闭环" in payload["current_version_content"]
def test_activate_pending_rule_endpoint_is_blocked() -> None:
client, session_factory = build_client()
with session_factory() as db:
pending_rule = next(
item
for item in AgentAssetService(db).list_assets(asset_type="rule")
if item.status == AgentAssetStatus.REVIEW.value
)
response = client.post(
f"/api/v1/agent-assets/{pending_rule.id}/activate",
headers={"x-actor": "pytest"},
)
assert response.status_code == 400
assert "审核" in response.json()["detail"]
def test_list_audit_logs_endpoint_returns_seeded_logs() -> None:
client, _ = build_client()
response = client.get("/api/v1/audit-logs")
assert response.status_code == 200
payload = response.json()
assert payload
assert any(item["action"] == "review_rule" for item in payload)

File diff suppressed because it is too large Load Diff