Compare commits
59 Commits
08a4fa3577
...
fix/multi-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ced9d93bc | ||
|
|
876cf342ac | ||
|
|
43779f8f2c | ||
|
|
08f023243e | ||
|
|
6bdaeed6d4 | ||
|
|
d5a8f84703 | ||
|
|
c4b5fcc067 | ||
|
|
5753899eb3 | ||
|
|
9c3fa80d22 | ||
|
|
43c3ff860c | ||
|
|
3e4b1e1597 | ||
|
|
3a5664c4da | ||
|
|
d139a63e64 | ||
|
|
8a2ae6eb75 | ||
|
|
992cf71fa1 | ||
|
|
54356ba81a | ||
|
|
e9d7c56d5b | ||
|
|
2ebc2756bf | ||
|
|
606a88c805 | ||
|
|
eaada4bc57 | ||
|
|
d321005044 | ||
|
|
59353308a2 | ||
|
|
6b0756a55f | ||
|
|
4d8a606cd6 | ||
|
|
23f7de6cbf | ||
|
|
a12c4bea64 | ||
|
|
e5b03c6601 | ||
|
|
3eb78d343a | ||
|
|
a0f6d9f702 | ||
|
|
bb681aa1f3 | ||
|
|
bc560145a4 | ||
|
|
5311c99d69 | ||
|
|
545b31d32f | ||
|
|
8417a9f542 | ||
|
|
9a5ed0e94a | ||
|
|
50d2dc579a | ||
|
|
f9553a6a1a | ||
|
|
ee730aa31c | ||
|
|
0264a4b5b4 | ||
|
|
332f77389d | ||
|
|
d4ff79f326 | ||
|
|
93212600eb | ||
|
|
73966b3a7b | ||
|
|
1f40ce3df3 | ||
|
|
f17098aa58 | ||
|
|
8094333e3b | ||
|
|
0122f3b250 | ||
|
|
dc4cad2baa | ||
|
|
e725b7f19c | ||
|
|
84a8998e59 | ||
|
|
bc743adef3 | ||
|
|
ded8b39ccb | ||
|
|
ba444a514f | ||
|
|
aa965da69d | ||
|
|
1b04ee1c4c | ||
|
|
103f225f54 | ||
|
|
e42dedaba1 | ||
|
|
607e127f59 | ||
|
|
6d33ba5742 |
160
.codex/skills/agent-change-log/SKILL.md
Normal file
160
.codex/skills/agent-change-log/SKILL.md
Normal file
@@ -0,0 +1,160 @@
|
||||
---
|
||||
name: agent-change-log
|
||||
description: Use when working in X-Financial after bug fixes that need a split dev log, when maintaining document/development/YYYY-MM-DD/dev-logs/bugs, or when generating the daily 17:00 combined work-logs.med from same-day feature docs and bug logs.
|
||||
---
|
||||
|
||||
# Agent Change Log
|
||||
|
||||
## Overview
|
||||
|
||||
This skill keeps split development logs for X-Financial. Bug fixes are recorded under the same-day `dev-logs/bugs` folder. Daily summaries combine same-day feature documents and bug records into one `work-logs.med`.
|
||||
|
||||
The log should sound like a careful teammate writing for tomorrow's teammate: concrete, warm, and honest.
|
||||
|
||||
## When To Use
|
||||
|
||||
- After fixing a bug.
|
||||
- When a post-commit hook detects a bug-like commit.
|
||||
- At the daily 17:00 summary pass.
|
||||
- Before updating the log in a branch where other agents may have pushed commits.
|
||||
- After verification, so the entry can include what was actually checked.
|
||||
- When a failed attempt changed files, generated artifacts, or revealed a risk worth preserving.
|
||||
|
||||
Do not create legacy `document/work-log/YYYY-MM-DD.md` entries for new work.
|
||||
|
||||
## Log Location
|
||||
|
||||
Use the same date root as development documents:
|
||||
|
||||
```text
|
||||
document/development/YYYY-MM-DD/
|
||||
├── feature/<feature-point>/CONCEPT.md + TODO.md
|
||||
├── dev-logs/bugs/<bug-slug>.md
|
||||
└── work-logs.med
|
||||
```
|
||||
|
||||
If the date folder exists, reuse it. If not, create it.
|
||||
|
||||
## Bug Log Structure
|
||||
|
||||
For bug fixes, create or update one file per bug:
|
||||
|
||||
```text
|
||||
document/development/YYYY-MM-DD/dev-logs/bugs/<bug-slug>.md
|
||||
```
|
||||
|
||||
The file must contain `## 修复记录` and timestamped bullets similar to the old `当日工作内容`.
|
||||
Do not add `遗留问题` or `TODO` sections to bug logs.
|
||||
|
||||
Use the helper when possible:
|
||||
|
||||
```bash
|
||||
python3 tools/agent-change-log/update_change_log.py \
|
||||
--kind bug \
|
||||
--bug-title "<bug 名称>" \
|
||||
--bug-slug <bug-slug>
|
||||
```
|
||||
|
||||
## Required Git Check
|
||||
|
||||
Before writing or updating a bug log, manually check Git for upstream and local-ahead commits from other agents.
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
date '+%Y-%m-%d %H:%M:%S %Z'
|
||||
git fetch --all --prune
|
||||
git status -sb
|
||||
git rev-parse --abbrev-ref --symbolic-full-name @{u}
|
||||
git log --oneline --decorate --max-count=20 HEAD..@{u}
|
||||
git log --oneline --decorate --max-count=20 @{u}..HEAD
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- Treat `git fetch --all --prune` as the default safe "pull check"; it updates remote refs without merging into a dirty worktree.
|
||||
- Treat `HEAD..@{u}` as upstream commits not yet in local history.
|
||||
- Treat `@{u}..HEAD` as local commits not yet in upstream history; these may also come from another agent working in the same checkout.
|
||||
- If the worktree is clean and the branch is only behind upstream, `git pull --ff-only` may be used to fast-forward before analysis.
|
||||
- If the worktree is dirty, diverged, or likely to conflict, do not merge/rebase automatically. Record the upstream commits from `HEAD..@{u}` in the bug fix entry.
|
||||
- If there is no upstream branch, record that fact in the bug fix entry and continue with local-only logging.
|
||||
- When `HEAD..@{u}` has commits, summarize those commits in the bug fix entry before describing local edits. Mention commit hash, subject, and inferred impact.
|
||||
- When `@{u}..HEAD` has commits that were not created in the current task, summarize them too, because another local agent may have committed without pushing yet.
|
||||
- When no upstream or local-ahead commits exist, still record "Git 提交检查:未发现 upstream 新提交或本地 ahead 新提交" in the work entry.
|
||||
|
||||
## Bug Entry Rules
|
||||
|
||||
1. Get the current local time first:
|
||||
```bash
|
||||
date '+%Y-%m-%d %H:%M:%S %Z'
|
||||
```
|
||||
2. Run the required Git check and capture whether upstream or local-ahead has new commits.
|
||||
3. Ensure `document/development/YYYY-MM-DD/dev-logs/bugs/` exists.
|
||||
4. Create or update one `<bug-slug>.md` file for the specific bug.
|
||||
5. Append a new timestamped bullet under `## 修复记录`.
|
||||
6. Mention Git commits, changed files or modules, the operation, the intent, and the verification result.
|
||||
7. Do not add leftover issue or TODO sections.
|
||||
|
||||
## Daily Summary
|
||||
|
||||
At 17:00 every day, generate the combined work log:
|
||||
|
||||
```bash
|
||||
python3 tools/agent-change-log/update_change_log.py --kind summary
|
||||
```
|
||||
|
||||
This reads:
|
||||
|
||||
```text
|
||||
document/development/YYYY-MM-DD/feature/
|
||||
document/development/YYYY-MM-DD/dev-logs/bugs/
|
||||
```
|
||||
|
||||
Then writes:
|
||||
|
||||
```text
|
||||
document/development/YYYY-MM-DD/work-logs.med
|
||||
```
|
||||
|
||||
The summary should cover:
|
||||
|
||||
- Today's feature points from `feature/*/CONCEPT.md` and `TODO.md`.
|
||||
- Today's bug fixes from `dev-logs/bugs/*.md`.
|
||||
- A concise combined analysis of what changed that day.
|
||||
|
||||
## Entry Rules
|
||||
|
||||
- For each bug entry, append a new timestamped bullet under `## 修复记录`.
|
||||
- Mention Git commits, changed files or modules, the operation, the intent, and the verification result.
|
||||
- Do not write `遗留问题`.
|
||||
- Do not write `TODO`.
|
||||
- If the change is a feature rather than a bug, use the development document skill to keep `feature/<feature-point>/CONCEPT.md` and `TODO.md` current instead of writing a bug log.
|
||||
|
||||
## Writing Style
|
||||
|
||||
- Write in Simplified Chinese.
|
||||
- Be specific and a little human: "我把...", "这次先...", "还需要留意..." are good.
|
||||
- Keep the tone factual. Do not turn the log into a victory lap.
|
||||
- Prefer concise file names and module names in prose, but include enough context to find the change.
|
||||
- Work content should be detailed enough that a future agent can continue without asking "你到底改了啥?"
|
||||
|
||||
## Bug Entry Template
|
||||
|
||||
```markdown
|
||||
- HH:MM:记录 bug 修复:<bug 名称>。
|
||||
- Git 提交检查:<git fetch 后 HEAD..upstream 与 upstream..HEAD 的结果;没有就写未发现 upstream 或本地 ahead 新提交>。
|
||||
- 修改:<文件/模块>,<做了什么>。
|
||||
- 操作:<运行了什么命令、迁移了什么状态、重启了什么服务等>。
|
||||
- 验证:<测试/构建/检查结果;如果没跑,说明原因>。
|
||||
- 影响:<用户可见变化或工程边界变化>。
|
||||
```
|
||||
|
||||
## Final Response Checklist
|
||||
|
||||
Before saying work is complete:
|
||||
|
||||
- For bug fixes, today's bug log exists under `document/development/YYYY-MM-DD/dev-logs/bugs/`.
|
||||
- For non-bug feature work, relevant `feature/<feature-point>` documents are current.
|
||||
- Git check ran for bug logs, and upstream plus local-ahead commits were summarized or explicitly marked as absent.
|
||||
- No new legacy `document/work-log/YYYY-MM-DD.md` entry was created.
|
||||
- The final response mentions whether a bug log, feature document, or daily `work-logs.med` was updated.
|
||||
4
.codex/skills/agent-change-log/agents/openai.yaml
Normal file
4
.codex/skills/agent-change-log/agents/openai.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
interface:
|
||||
display_name: "Agent Change Log"
|
||||
short_description: "Record bug logs and daily work summaries"
|
||||
default_prompt: "Use $agent-change-log after an X-Financial bug fix or at 17:00 to update document/development/<date>/dev-logs/bugs or work-logs.med."
|
||||
85
.codex/skills/git-checkpoint-commit/SKILL.md
Normal file
85
.codex/skills/git-checkpoint-commit/SKILL.md
Normal file
@@ -0,0 +1,85 @@
|
||||
---
|
||||
name: git-checkpoint-commit
|
||||
description: Use when a coding task finishes a verified bug fix, key feature, risky refactor checkpoint, local backup commit, checkpoint commit, 提交备份, 本地提交, or when an active session has accumulated about five meaningful edit rounds and should create a scoped local git commit without pushing.
|
||||
---
|
||||
|
||||
# Git Checkpoint Commit
|
||||
|
||||
## Overview
|
||||
|
||||
Use this skill to keep a local git backup loop during active development. The goal is to commit verified, task-scoped progress at meaningful checkpoints without mixing unrelated user changes or pushing remotely.
|
||||
|
||||
## Trigger Rules
|
||||
|
||||
Create a local checkpoint commit when any condition is true:
|
||||
|
||||
- A bug fix is implemented and the targeted regression check passes.
|
||||
- A key feature slice is implemented and the smallest relevant verification passes.
|
||||
- A risky refactor reaches a behavior-preserving checkpoint.
|
||||
- The current session has reached about five meaningful edit rounds since the last commit.
|
||||
- The user asks for `提交`, `本地提交`, `备份`, `checkpoint`, `commit`, or `形成提交循环`.
|
||||
|
||||
Do not use this skill when the user explicitly says not to commit, when the change is exploratory and unverified, or when the only available commit would include unrelated dirty files.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Inspect the working tree with `git status --short`.
|
||||
2. Identify files or hunks owned by the current task.
|
||||
3. Run the smallest relevant verification first.
|
||||
- In X-Financial, run backend tests inside `x-financial-main` when backend code is involved.
|
||||
- Use targeted frontend tests/builds for web-only changes.
|
||||
4. Commit only the task-owned files.
|
||||
5. Report the commit hash and verification evidence.
|
||||
6. Reset the session edit counter to zero after a successful checkpoint.
|
||||
|
||||
## Safety Rules
|
||||
|
||||
- Never commit unrelated user changes just to make the tree clean.
|
||||
- Never push as part of this skill.
|
||||
- Never rewrite history, amend old commits, or run destructive git commands.
|
||||
- If the same file contains unrelated hunks, split the staging carefully with `git add -p` or a narrower manual patch.
|
||||
- If the index already contains staged changes, inspect them first; do not mix them into a checkpoint unless they belong to the same current task.
|
||||
- If verification cannot run, say why in the commit body or final report and prefer a `chore(checkpoint)` message rather than a confident `fix` or `feat`.
|
||||
|
||||
## Commit Style
|
||||
|
||||
Use normal semantic messages for completed, verified work:
|
||||
|
||||
- `fix(workbench): keep application preview after draft save failure`
|
||||
- `feat(reimbursements): add attachment association job polling`
|
||||
- `refactor(claims): split draft flow serialization`
|
||||
|
||||
Use checkpoint messages for interim backup points:
|
||||
|
||||
- `chore(checkpoint): backup attachment association flow`
|
||||
- `chore(checkpoint): backup after five edit rounds`
|
||||
|
||||
Keep the subject concise. Add body lines only when the verification state or scope needs clarity.
|
||||
|
||||
## Helper Script
|
||||
|
||||
Use `scripts/checkpoint_commit.py` when a path-scoped local commit is enough:
|
||||
|
||||
```bash
|
||||
python3 .codex/skills/git-checkpoint-commit/scripts/checkpoint_commit.py \
|
||||
--message "chore(checkpoint): backup workbench AI flow" \
|
||||
web/src/composables/workbenchAiMode/useWorkbenchAiExpenseFlow.js \
|
||||
web/tests/workbench-ai-mode-switch.test.mjs
|
||||
```
|
||||
|
||||
Preview before committing:
|
||||
|
||||
```bash
|
||||
python3 .codex/skills/git-checkpoint-commit/scripts/checkpoint_commit.py \
|
||||
--dry-run \
|
||||
web/src/utils/expenseApplicationPreview.js
|
||||
```
|
||||
|
||||
The script refuses to commit the whole tree unless `--allow-all` is passed. Use `--allow-all` only when the current task truly owns every dirty file.
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
- Mistaking backup for verification. Verify first, then commit.
|
||||
- Staging `.` in a dirty worktree. Stage explicit paths or hunks.
|
||||
- Combining documentation cleanup, feature work, and unrelated local edits in one checkpoint.
|
||||
- Continuing indefinitely after multiple verified slices. Commit once the counter reaches five meaningful edit rounds.
|
||||
4
.codex/skills/git-checkpoint-commit/agents/openai.yaml
Normal file
4
.codex/skills/git-checkpoint-commit/agents/openai.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
interface:
|
||||
display_name: "Git Checkpoint Commit"
|
||||
short_description: "Create local backup commits at safe milestones"
|
||||
default_prompt: "Use $git-checkpoint-commit to checkpoint verified task changes into a local git commit."
|
||||
127
.codex/skills/git-checkpoint-commit/scripts/checkpoint_commit.py
Normal file
127
.codex/skills/git-checkpoint-commit/scripts/checkpoint_commit.py
Normal file
@@ -0,0 +1,127 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Create a path-scoped local git checkpoint commit."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def run_git(args: list[str], cwd: Path, *, check: bool = True) -> subprocess.CompletedProcess[str]:
|
||||
result = subprocess.run(
|
||||
["git", *args],
|
||||
cwd=cwd,
|
||||
text=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
timeout=60,
|
||||
)
|
||||
if check and result.returncode != 0:
|
||||
message = result.stderr.strip() or result.stdout.strip()
|
||||
raise SystemExit(f"git {' '.join(args)} failed: {message}")
|
||||
return result
|
||||
|
||||
|
||||
def repo_root() -> Path:
|
||||
result = subprocess.run(
|
||||
["git", "rev-parse", "--show-toplevel"],
|
||||
text=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
timeout=60,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
raise SystemExit("Not inside a git repository.")
|
||||
return Path(result.stdout.strip())
|
||||
|
||||
|
||||
def load_paths(args: argparse.Namespace) -> list[str]:
|
||||
paths = list(args.paths)
|
||||
if args.paths_from_file:
|
||||
raw_lines = Path(args.paths_from_file).read_text(encoding="utf-8").splitlines()
|
||||
paths.extend(line.strip() for line in raw_lines if line.strip() and not line.startswith("#"))
|
||||
return paths
|
||||
|
||||
|
||||
def ensure_clean_index(root: Path) -> None:
|
||||
staged = run_git(["diff", "--cached", "--name-only"], root).stdout.strip()
|
||||
if staged:
|
||||
raise SystemExit(
|
||||
"Refusing to continue because the index already has staged changes:\n"
|
||||
f"{staged}\n"
|
||||
"Commit or unstage them before running this checkpoint helper."
|
||||
)
|
||||
|
||||
|
||||
def status_for(root: Path, paths: list[str], allow_all: bool) -> str:
|
||||
command = ["status", "--short"]
|
||||
if not allow_all:
|
||||
command.extend(["--", *paths])
|
||||
return run_git(command, root).stdout.strip()
|
||||
|
||||
|
||||
def infer_message(paths: list[str], allow_all: bool) -> str:
|
||||
if allow_all or not paths:
|
||||
return "chore(checkpoint): backup current task changes"
|
||||
first_parts = [Path(item).parts[0] for item in paths if Path(item).parts]
|
||||
scope = first_parts[0] if first_parts else "task"
|
||||
return f"chore(checkpoint): backup {scope} changes"
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("paths", nargs="*", help="Task-owned paths to stage and commit.")
|
||||
parser.add_argument("-m", "--message", help="Commit message subject/body.")
|
||||
parser.add_argument("--paths-from-file", help="Read additional pathspecs from a UTF-8 text file.")
|
||||
parser.add_argument("--dry-run", action="store_true", help="Show matching changes without staging.")
|
||||
parser.add_argument("--allow-all", action="store_true", help="Allow committing all dirty files.")
|
||||
parser.add_argument("--no-verify", action="store_true", help="Pass --no-verify to git commit.")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
root = repo_root()
|
||||
paths = load_paths(args)
|
||||
|
||||
if not paths and not args.allow_all:
|
||||
raise SystemExit("Pass explicit paths, or use --allow-all when the current task owns all changes.")
|
||||
|
||||
current_status = status_for(root, paths, args.allow_all)
|
||||
if not current_status:
|
||||
print("No matching changes to commit.")
|
||||
return 0
|
||||
|
||||
print(current_status)
|
||||
if args.dry_run:
|
||||
return 0
|
||||
|
||||
ensure_clean_index(root)
|
||||
|
||||
# 使用 -A 保留删除/重命名等变更,但只作用于明确传入的 pathspec。
|
||||
if args.allow_all:
|
||||
run_git(["add", "-A"], root)
|
||||
else:
|
||||
run_git(["add", "-A", "--", *paths], root)
|
||||
|
||||
staged_summary = run_git(["diff", "--cached", "--name-status"], root).stdout.strip()
|
||||
if not staged_summary:
|
||||
raise SystemExit("No staged changes after git add.")
|
||||
|
||||
message = args.message or infer_message(paths, args.allow_all)
|
||||
commit_command = ["commit"]
|
||||
if args.no_verify:
|
||||
commit_command.append("--no-verify")
|
||||
commit_command.extend(["-m", message])
|
||||
commit_result = run_git(commit_command, root)
|
||||
sys.stdout.write(commit_result.stdout)
|
||||
|
||||
commit_hash = run_git(["rev-parse", "--short", "HEAD"], root).stdout.strip()
|
||||
print(f"checkpoint_commit={commit_hash}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
125
.codex/skills/write-development-docs/SKILL.md
Normal file
125
.codex/skills/write-development-docs/SKILL.md
Normal file
@@ -0,0 +1,125 @@
|
||||
---
|
||||
name: write-development-docs
|
||||
description: Use when working in X-Financial and the user asks to 落文档, 写开发文档, 沉淀方案, 补 concept/todo, or create/update planning documentation under document/development for a feature, refactor, page, algorithm, rule, or business capability.
|
||||
---
|
||||
|
||||
# Write Development Docs
|
||||
|
||||
## 目标
|
||||
|
||||
把一个功能、重构、算法、页面或业务能力沉淀为项目标准开发文档。
|
||||
默认落点固定为:
|
||||
|
||||
```text
|
||||
document/development/<YYYY-MM-DD>/feature/<具体功能点目录>/
|
||||
├── CONCEPT.md
|
||||
└── TODO.md
|
||||
```
|
||||
|
||||
如果用户说 `concept.md` / `todo.md`,也按仓库现有规范使用大写文件名
|
||||
`CONCEPT.md` 和 `TODO.md`。
|
||||
|
||||
## 工作流
|
||||
|
||||
1. 先读取当前日期,默认使用本地日期 `YYYY-MM-DD` 作为第一层目录。
|
||||
2. 先阅读 `document/development` 中 2-3 组相邻或同类样例。
|
||||
3. 再读取本次能力相关的代码、接口、页面、测试或历史文档。
|
||||
4. 创建或更新 `CONCEPT.md`,先写清业务边界,再写方案和验收。
|
||||
5. 创建或更新 `TODO.md`,每个任务都要回链到 `CONCEPT.md` 的章节。
|
||||
6. 已实现或已验证的 TODO 可以勾选 `[x]`,但必须写证据。
|
||||
7. 交付前检查两份文档互相一致,不额外创建 README、CHANGELOG、SUMMARY。
|
||||
|
||||
## 路径规则
|
||||
|
||||
- 第一层必须是日期目录,例如 `document/development/2026-06-25/`。
|
||||
- 第二层固定是 `feature/`,表示当天沉淀的功能点集合。
|
||||
- 第三层是具体功能点目录,每个独立功能点一个目录。
|
||||
- 每个具体功能点目录内只放 `CONCEPT.md` 和 `TODO.md` 两个核心文件。
|
||||
- 如果一次请求包含多个互不依赖的功能点,拆成多个兄弟目录:
|
||||
|
||||
```text
|
||||
document/development/2026-06-25/feature/
|
||||
├── receipt-folder-ocr/
|
||||
│ ├── CONCEPT.md
|
||||
│ └── TODO.md
|
||||
└── risk-review-nudge/
|
||||
├── CONCEPT.md
|
||||
└── TODO.md
|
||||
```
|
||||
|
||||
- 如果用户明确指定日期,使用用户指定日期;否则使用当前本地日期。
|
||||
- 如果是更新历史文档,先查找已有目录并原地更新,不自动迁移旧路径。
|
||||
|
||||
## 目录命名
|
||||
|
||||
- 优先复用用户指定目录名或已有目录名。
|
||||
- 新增英文目录用小写 kebab-case,例如 `receipt-folder`。
|
||||
- 新增中文目录可直接用清晰中文能力名,例如 `费用审批动态路由`。
|
||||
- 同一能力已有目录时更新原目录,不新建近义目录。
|
||||
|
||||
## CONCEPT.md 要求
|
||||
|
||||
可参考 `assets/CONCEPT.md` 模板。必须包含:
|
||||
|
||||
- 标题:`# <功能名> 概念文档`
|
||||
- `更新时间:YYYY-MM-DD`
|
||||
- `## 功能一句话`
|
||||
- `## 背景与问题`
|
||||
- `## 目标与非目标`
|
||||
- `## 用户与场景`
|
||||
- `## 功能能力`
|
||||
- `## 方案设计`
|
||||
- `## 算法与公式`
|
||||
- `## 测试方案`
|
||||
- `## 指标与验收`
|
||||
- `## 风险与开放问题`
|
||||
|
||||
写法要求:
|
||||
|
||||
- 先讲业务问题和边界,再讲技术方案。
|
||||
- 目标与非目标分开写,避免需求无限扩张。
|
||||
- 方案设计按前端、后端、算法/规则、数据、权限、降级策略分块;
|
||||
不涉及的块明确写“当前不涉及”。
|
||||
- 算法与公式必须明确“不涉及”或写出公式、变量说明和适用边界。
|
||||
- 验收标准必须可验证,不写空泛口号。
|
||||
|
||||
## TODO.md 要求
|
||||
|
||||
可参考 `assets/TODO.md` 模板。必须包含:
|
||||
|
||||
- 标题:`# <功能名> 开发 TODO`
|
||||
- `更新时间:YYYY-MM-DD`
|
||||
- `## 使用规则`
|
||||
- 分阶段 checklist
|
||||
|
||||
TODO 条目规则:
|
||||
|
||||
- 每条用 `- [ ]` 或 `- [x]`。
|
||||
- 每条必须包含 `[CONCEPT: <章节名>]`。
|
||||
- 已完成项必须补证据,格式为 `证据:<文件、接口、命令或验证结果>`。
|
||||
- 没有真实证据时不得勾选 `[x]`。
|
||||
|
||||
建议阶段:
|
||||
|
||||
- `## 1. 调研与边界`
|
||||
- `## 2. 契约与设计`
|
||||
- `## 3. 后端实现`
|
||||
- `## 4. 算法/规则实现`
|
||||
- `## 5. 前端实现`
|
||||
- `## 6. 测试与验证`
|
||||
- `## 7. 文档收尾`
|
||||
|
||||
## 更新既有文档
|
||||
|
||||
- 先读现有 `CONCEPT.md` 和 `TODO.md` 全文。
|
||||
- 新需求先补 `CONCEPT.md`,再补 `TODO.md`。
|
||||
- 实现变化时同步更新“非目标”“风险与开放问题”“本轮实现记录”。
|
||||
- 不删除历史证据;除非证据明显错误,才替换为新证据。
|
||||
|
||||
## 验收检查
|
||||
|
||||
- 新建文档路径符合 `document/development/<YYYY-MM-DD>/feature/<具体功能点目录>/`。
|
||||
- `CONCEPT.md` 和 `TODO.md` 都存在于同一个具体功能点目录。
|
||||
- TODO 的 `[CONCEPT: ...]` 都能在 CONCEPT 中找到对应章节或语义段落。
|
||||
- 已勾选项都有证据。
|
||||
- 文档没有遗留模板占位符,例如 `<功能名>`、`<YYYY-MM-DD>`、`待补充`。
|
||||
4
.codex/skills/write-development-docs/agents/openai.yaml
Normal file
4
.codex/skills/write-development-docs/agents/openai.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
interface:
|
||||
display_name: "开发文档落地"
|
||||
short_description: "按日期和功能点生成 CONCEPT/TODO"
|
||||
default_prompt: "Use $write-development-docs to create CONCEPT.md and TODO.md under document/development/<date>/feature/<feature-point> for an X-Financial feature."
|
||||
132
.codex/skills/write-development-docs/assets/CONCEPT.md
Normal file
132
.codex/skills/write-development-docs/assets/CONCEPT.md
Normal file
@@ -0,0 +1,132 @@
|
||||
# <功能名> 概念文档
|
||||
|
||||
更新时间:<YYYY-MM-DD>
|
||||
|
||||
文档路径:document/development/<YYYY-MM-DD>/feature/<具体功能点目录>/CONCEPT.md
|
||||
|
||||
## 功能一句话
|
||||
|
||||
用一句话说明这个能力解决什么问题、服务谁、交付什么结果。
|
||||
|
||||
## 背景与问题
|
||||
|
||||
- 当前现状:
|
||||
- 用户痛点:
|
||||
- 业务影响:
|
||||
- 为什么现在需要做:
|
||||
|
||||
## 目标与非目标
|
||||
|
||||
### 目标
|
||||
|
||||
- [G1]
|
||||
- [G2]
|
||||
- [G3]
|
||||
|
||||
### 非目标
|
||||
|
||||
- [NG1] 本轮不做:
|
||||
- [NG2] 本轮不改变:
|
||||
- [NG3] 后续再评估:
|
||||
|
||||
## 用户与场景
|
||||
|
||||
- 目标用户:
|
||||
- 使用入口:
|
||||
- 核心场景:
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
- 异常场景:
|
||||
-
|
||||
|
||||
## 功能能力
|
||||
|
||||
- [C1] 输入能力:
|
||||
- [C2] 处理能力:
|
||||
- [C3] 输出能力:
|
||||
- [C4] 状态与权限:
|
||||
- [C5] 边界与降级:
|
||||
|
||||
## 方案设计
|
||||
|
||||
### 前端
|
||||
|
||||
- 页面/组件:
|
||||
- 交互状态:
|
||||
- 展示规则:
|
||||
- 降级处理:
|
||||
|
||||
### 后端
|
||||
|
||||
- 接口/服务:
|
||||
- 权限与校验:
|
||||
- 持久化:
|
||||
- 降级处理:
|
||||
|
||||
### 算法与规则
|
||||
|
||||
- 输入:
|
||||
- 流程:
|
||||
- 输出:
|
||||
- 解释:
|
||||
|
||||
### 数据与契约
|
||||
|
||||
- 核心字段:
|
||||
- 状态枚举:
|
||||
- 兼容策略:
|
||||
- 版本/审计:
|
||||
|
||||
## 算法与公式
|
||||
|
||||
当前功能不涉及显式数学公式。
|
||||
|
||||
如涉及公式,使用如下格式:
|
||||
|
||||
```text
|
||||
metric = input_a + input_b
|
||||
```
|
||||
|
||||
变量说明:
|
||||
|
||||
- metric:
|
||||
- input_a:
|
||||
- input_b:
|
||||
|
||||
## 测试方案
|
||||
|
||||
后端:
|
||||
|
||||
-
|
||||
|
||||
前端:
|
||||
|
||||
-
|
||||
|
||||
集成:
|
||||
|
||||
-
|
||||
|
||||
手工验证:
|
||||
|
||||
-
|
||||
|
||||
## 指标与验收
|
||||
|
||||
- [A1] 功能验收:
|
||||
- [A2] 性能指标:
|
||||
- [A3] 质量指标:
|
||||
- [A4] 安全/权限指标:
|
||||
- [A5] 可观测性:
|
||||
|
||||
## 风险与开放问题
|
||||
|
||||
- 风险:
|
||||
- 已处理依赖:
|
||||
- 待确认:
|
||||
- 降级策略:
|
||||
|
||||
## 本轮实现记录
|
||||
|
||||
-
|
||||
73
.codex/skills/write-development-docs/assets/TODO.md
Normal file
73
.codex/skills/write-development-docs/assets/TODO.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# <功能名> 开发 TODO
|
||||
|
||||
更新时间:<YYYY-MM-DD>
|
||||
|
||||
文档路径:document/development/<YYYY-MM-DD>/feature/<具体功能点目录>/TODO.md
|
||||
|
||||
## 使用规则
|
||||
|
||||
- 每个 TODO 必须对应 `CONCEPT.md` 中的目标、能力、方案或验收点。
|
||||
- 只有完成并验证后,才能把 `[ ]` 改成 `[x]`。
|
||||
- 勾选时在任务后补充简短证据,例如文件、接口、命令或验证结果。
|
||||
- 如果需求发生变化,先更新 `CONCEPT.md`,再调整本 TODO。
|
||||
|
||||
## 1. 调研与边界
|
||||
|
||||
- [ ] [CONCEPT: 背景与问题] 阅读相关页面、接口、服务、测试和历史文档,记录当前实现事实。
|
||||
证据:
|
||||
- [ ] [CONCEPT: 目标与非目标] 确认本轮开发范围,写清楚不做项。
|
||||
证据:
|
||||
- [ ] [CONCEPT: 风险与开放问题] 标记无法立即确认的依赖、风险和假设。
|
||||
证据:
|
||||
|
||||
## 2. 契约与设计
|
||||
|
||||
- [ ] [CONCEPT: 功能能力] 定义输入、输出、状态、权限和边界条件。
|
||||
证据:
|
||||
- [ ] [CONCEPT: 方案设计] 明确前端、后端、算法、数据的职责边界。
|
||||
证据:
|
||||
- [ ] [CONCEPT: 算法与公式] 补全公式、变量解释或明确当前不涉及公式。
|
||||
证据:
|
||||
- [ ] [CONCEPT: 指标与验收] 把验收标准转成可验证的检查点。
|
||||
证据:
|
||||
|
||||
## 3. 后端实现
|
||||
|
||||
- [ ] [CONCEPT: 后端] 新增或调整 schema、service、endpoint、权限和持久化逻辑。
|
||||
证据:
|
||||
- [ ] [CONCEPT: 数据与契约] 保持响应结构、状态枚举和兼容策略清晰。
|
||||
证据:
|
||||
|
||||
## 4. 算法/规则实现
|
||||
|
||||
- [ ] [CONCEPT: 算法与规则] 实现核心处理流程、规则判断或计算逻辑。
|
||||
证据:
|
||||
- [ ] [CONCEPT: 结果解释] 输出可读解释、证据链、贡献项或降级原因。
|
||||
证据:
|
||||
|
||||
## 5. 前端实现
|
||||
|
||||
- [ ] [CONCEPT: 前端] 新增或调整页面、组件、服务 API 和视图模型。
|
||||
证据:
|
||||
- [ ] [CONCEPT: 前端] 实现加载、空态、错误态、权限态和刷新。
|
||||
证据:
|
||||
- [ ] [CONCEPT: 前端] 对齐现有企业后台风格,避免营销页或花哨卡片感。
|
||||
证据:
|
||||
|
||||
## 6. 测试与验证
|
||||
|
||||
- [ ] [CONCEPT: 测试方案] 补充后端 service/API 定向测试,容器内运行,超时控制在 60s 内。
|
||||
证据:
|
||||
- [ ] [CONCEPT: 测试方案] 补充前端视图模型、路由、组件或构建验证。
|
||||
证据:
|
||||
- [ ] [CONCEPT: 指标与验收] 记录验证命令、结果和未覆盖风险。
|
||||
证据:
|
||||
|
||||
## 7. 文档收尾
|
||||
|
||||
- [ ] [CONCEPT: 指标与验收] 回看所有验收点,确认均有实现或验证证据。
|
||||
证据:
|
||||
- [ ] [CONCEPT: 风险与开放问题] 更新剩余风险、后续任务和明确不做项。
|
||||
证据:
|
||||
- [ ] [CONCEPT: 功能一句话] 确认最终实现没有偏离原始目标。
|
||||
证据:
|
||||
@@ -14,9 +14,9 @@ VITE_ADMIN_EMAIL=
|
||||
# Admin login credentials are stored separately under server/.secrets/
|
||||
|
||||
WEB_HOST=0.0.0.0
|
||||
WEB_PORT=5273
|
||||
WEB_PORT=5173
|
||||
VITE_WEB_HOST=0.0.0.0
|
||||
VITE_WEB_PORT=5273
|
||||
VITE_WEB_PORT=5173
|
||||
|
||||
SERVER_HOST=0.0.0.0
|
||||
SERVER_PORT=8000
|
||||
@@ -31,6 +31,7 @@ ONLYOFFICE_PUBLIC_URL=http://127.0.0.1:8082
|
||||
ONLYOFFICE_BACKEND_URL=http://127.0.0.1:8000
|
||||
ONLYOFFICE_JWT_SECRET=change-me-onlyoffice
|
||||
HERMES_AGENT_SHARED_TOKEN=change-me-hermes
|
||||
STEWARD_AGENT_RUNTIME=langgraph
|
||||
|
||||
POSTGRES_HOST=127.0.0.1
|
||||
POSTGRES_PORT=5432
|
||||
@@ -52,4 +53,4 @@ OCR_DEVICE=
|
||||
OCR_TIMEOUT_SECONDS=180
|
||||
OCR_MAX_CONCURRENT_WORKERS=1
|
||||
|
||||
CORS_ORIGINS='["http://127.0.0.1:5273","http://localhost:5273","http://0.0.0.0:5273"]'
|
||||
CORS_ORIGINS='["http://127.0.0.1:5173","http://localhost:5173","http://0.0.0.0:5173"]'
|
||||
|
||||
10
.githooks/post-commit
Executable file
10
.githooks/post-commit
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
# Auto-append a minimal X-Financial agent work-log entry after each commit.
|
||||
|
||||
repo_root="$(git rev-parse --show-toplevel 2>/dev/null)" || exit 0
|
||||
cd "$repo_root" || exit 0
|
||||
|
||||
python3 tools/agent-change-log/update_change_log.py \
|
||||
--kind auto \
|
||||
--event "post-commit hook" \
|
||||
>/tmp/x-financial-agent-change-log-hook.log 2>&1 || true
|
||||
13
.gitignore
vendored
13
.gitignore
vendored
@@ -7,7 +7,16 @@ web/.vite/
|
||||
.omc/
|
||||
.omx/
|
||||
.claude/
|
||||
.codex/
|
||||
*.egg-info/
|
||||
.codex/*
|
||||
!.codex/skills/
|
||||
.codex/skills/*
|
||||
!.codex/skills/agent-change-log/
|
||||
!.codex/skills/agent-change-log/**
|
||||
!.codex/skills/git-checkpoint-commit/
|
||||
!.codex/skills/git-checkpoint-commit/**
|
||||
!.codex/skills/write-development-docs/
|
||||
!.codex/skills/write-development-docs/**
|
||||
.codex-temp/
|
||||
.superpowers/
|
||||
*.log
|
||||
@@ -25,6 +34,8 @@ server/storage/receipt_folder/
|
||||
test-results/
|
||||
.codex-remote-attachments/
|
||||
tmp-*.png
|
||||
tmp/
|
||||
.zcode/
|
||||
.nezha/
|
||||
.omo/
|
||||
.env
|
||||
|
||||
19
AGENTS.md
19
AGENTS.md
@@ -5,6 +5,15 @@
|
||||
- 所有分析、解释、计划、提交说明和最终回复默认使用简体中文。
|
||||
- 技术结论要直击重点,必要时给出可验证的文件、命令或测试结果。
|
||||
|
||||
## 变更日志 Skill 规范
|
||||
|
||||
- 每次修复 bug 后,必须调用项目级 Skill `agent-change-log`,并在 `document/development/YYYY-MM-DD/dev-logs/bugs/<bug-slug>.md` 记录该 bug 的修复内容。
|
||||
- 新增功能、重构、配置或项目文档变更不再写入旧的 `document/work-log/YYYY-MM-DD.md`;功能点默认沉淀到 `document/development/YYYY-MM-DD/feature/<具体功能点>/CONCEPT.md` 和 `TODO.md`。
|
||||
- 写 bug 日志前必须先执行 Git 拉取检查:默认运行 `git fetch --all --prune`、`git status -sb`、`git log HEAD..@{u}` 和 `git log @{u}..HEAD`。发现其他智能体已提交到上游或本地 ahead 提交时,要把这些提交摘要写进 bug 修复记录。
|
||||
- 自动化触发由 `tools/agent-change-log/update_change_log.py` 和 `.githooks/post-commit` 提供;新 checkout 需要执行 `tools/agent-change-log/install_post_commit_hook.sh` 安装到本地 `.git/hooks/post-commit` 后,提交后才会按 `--kind auto` 自动识别 bug-like commit 并写入 `dev-logs/bugs`。
|
||||
- bug 日志只保留 `## 修复记录`,记录具体时间、改了什么、操作了什么、验证了什么和影响;不再写 `遗留问题` 和 `TODO` 两块。
|
||||
- 每天 17:00 生成当天综合日志:读取 `document/development/YYYY-MM-DD/feature/` 和 `document/development/YYYY-MM-DD/dev-logs/bugs/`,分析功能点与 bug 修复后写入 `document/development/YYYY-MM-DD/work-logs.med`。
|
||||
|
||||
## 通用代码拆分规范
|
||||
|
||||
无论写前端、后端还是算法代码,都必须主动避免“所有方法堆在一个类里 / 一个组件里 / 一个模块里”的写法。遇到类、组件或核心模块持续变大时,优先按职责拆分,而不是继续追加方法和状态。
|
||||
@@ -34,7 +43,7 @@
|
||||
|
||||
## 容器与运行环境(必读)
|
||||
|
||||
本项目代码是 Docker 容器 `x-financial-main`(镜像 `x-financial-dev:latest`)的源码映射。
|
||||
本项目代码是 Docker 容器 `local-x-financial-linux`(镜像 `x-financial-dev:latest`)的源码映射。
|
||||
|
||||
- **容器映射**:宿主机 `D:\Code\Project\X-Financial` ↔ 容器内 `/app`(`docker-compose.yml` 中 `volumes: - .:/app`,`working_dir: /app`)。
|
||||
- **后端 venv**:容器内位于 `/tmp/x-financial-server-venv`(环境变量 `SERVER_VENV_DIR`),不要假设宿主机上有相同的 venv。
|
||||
@@ -42,14 +51,14 @@
|
||||
|
||||
## 验证规范(硬性约束)
|
||||
|
||||
> 本项目代码与运行环境以容器为唯一事实来源。所有后端测试、集成测试、依赖了 Qdrant / OnlyOffice / venv 的验证,都必须在 `x-financial-main` 容器内执行,**不要在宿主机上直接跑 pytest / pip / python**。
|
||||
> 本项目代码与运行环境以容器为唯一事实来源。所有后端测试、集成测试、依赖了 Qdrant / OnlyOffice / venv 的验证,都必须在 `local-x-financial-linux` 容器内执行,**不要在宿主机上直接跑 pytest / pip / python**。
|
||||
|
||||
- **进入容器跑命令**(最常用):
|
||||
```bash
|
||||
docker exec -w /app -e SERVER_VENV_DIR=/tmp/x-financial-server-venv x-financial-main <cmd>
|
||||
docker exec -w /app -e SERVER_VENV_DIR=/tmp/x-financial-server-venv local-x-financial-linux <cmd>
|
||||
```
|
||||
- 跑后端测试:`docker exec -w /app -e SERVER_VENV_DIR=/tmp/x-financial-server-venv x-financial-main /tmp/x-financial-server-venv/bin/pytest -q <path>`
|
||||
- 交互式排查:`docker exec -it -w /app x-financial-main bash`(登录后默认已在 `/app`)
|
||||
- 跑后端测试:`docker exec -w /app -e SERVER_VENV_DIR=/tmp/x-financial-server-venv local-x-financial-linux /tmp/x-financial-server-venv/bin/pytest -q <path>`
|
||||
- 交互式排查:`docker exec -it -w /app local-x-financial-linux bash`(登录后默认已在 `/app`)
|
||||
- **容器不可用时**(未启动、健康检查失败、镜像丢失):先 `docker compose up -d main` 恢复,再继续验证;不要绕开容器在宿主机另装 venv。
|
||||
- **单元测试设置合理超时**,避免长时间卡死。涉及外部服务(Qdrant / OnlyOffice / LLM)的测试要么 mock,要么确认 compose 网络中依赖服务在线。
|
||||
- **每次重构后至少运行对应服务的定向测试**;涉及公共协议时补充端到端或接口测试。
|
||||
|
||||
@@ -37,6 +37,11 @@
|
||||
|
||||
根目录 `start.sh` 是统一编排入口;前端和后端的子启动脚本分别是 `web/web_start.sh` 与 `server/server_start.sh`。
|
||||
|
||||
Docker Compose 运行方式见 `docker/README.md`:
|
||||
|
||||
- `docker-compose.yml`:只启动主应用容器,适合复用已有数据库、ONLYOFFICE 等外部依赖。
|
||||
- `docker-compose.full.yml`:启动主应用、PostgreSQL、Qdrant、ONLYOFFICE 的完整本地开发栈。
|
||||
|
||||
手动进入前端目录:
|
||||
|
||||
```bash
|
||||
|
||||
144
docker-compose.full.yml
Normal file
144
docker-compose.full.yml
Normal file
@@ -0,0 +1,144 @@
|
||||
services:
|
||||
main:
|
||||
image: x-financial-dev:latest
|
||||
container_name: local-x-financial-linux
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
onlyoffice:
|
||||
condition: service_started
|
||||
qdrant:
|
||||
condition: service_started
|
||||
environment:
|
||||
WEB_HOST: 0.0.0.0
|
||||
WEB_PORT: "${WEB_PORT:-5173}"
|
||||
SERVER_HOST: 0.0.0.0
|
||||
SERVER_PORT: "${SERVER_PORT:-8000}"
|
||||
SERVER_RELOAD: "${SERVER_RELOAD:-true}"
|
||||
SERVER_VENV_DIR: /tmp/x-financial-server-venv
|
||||
X_FINANCIAL_PREFER_ENV_FILE: "false"
|
||||
POSTGRES_HOST: postgres
|
||||
POSTGRES_PORT: "5432"
|
||||
POSTGRES_DB: "${POSTGRES_DB:-x_financial}"
|
||||
POSTGRES_USER: "${POSTGRES_USER:-x_financial}"
|
||||
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:-x_financial}"
|
||||
DATABASE_URL: "postgresql+psycopg://${POSTGRES_USER:-x_financial}:${POSTGRES_PASSWORD:-x_financial}@postgres:5432/${POSTGRES_DB:-x_financial}"
|
||||
ONLYOFFICE_ENABLED: "true"
|
||||
ONLYOFFICE_PUBLIC_URL: "${LOCAL_ONLYOFFICE_PUBLIC_URL:-http://127.0.0.1:${ONLYOFFICE_PORT:-8082}}"
|
||||
ONLYOFFICE_BACKEND_URL: "${LOCAL_ONLYOFFICE_BACKEND_URL:-http://main:${SERVER_PORT:-8000}}"
|
||||
ONLYOFFICE_JWT_SECRET: "${ONLYOFFICE_JWT_SECRET:-x-financial-onlyoffice-dev-secret}"
|
||||
QDRANT_URL: "http://qdrant:6333"
|
||||
LIGHTRAG_WORKSPACE: "x_financial_knowledge"
|
||||
ports:
|
||||
- "${WEB_PORT:-5173}:${WEB_PORT:-5173}"
|
||||
- "${SERVER_PORT:-8000}:${SERVER_PORT:-8000}"
|
||||
- "2223:22"
|
||||
volumes:
|
||||
- .:/app
|
||||
working_dir: /app
|
||||
command:
|
||||
- /bin/sh
|
||||
- -lc
|
||||
- >
|
||||
apt-get update &&
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends
|
||||
python3 python3-pip python3-venv fontconfig openssh-server poppler-data mupdf-tools &&
|
||||
if ! fc-match 'Noto Sans CJK SC' | grep -qi 'Noto'; then if ! timeout "${CJK_FONT_INSTALL_TIMEOUT_SECONDS:-45}" sh -lc 'DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends fonts-noto-cjk fonts-noto-cjk-extra'; then printf '%s\n' '[WARN] CJK font installation timed out or failed; continuing startup without blocking the app.'; fi; fi &&
|
||||
printf '%s\n'
|
||||
'<?xml version="1.0"?>'
|
||||
'<!DOCTYPE fontconfig SYSTEM "fonts.dtd">'
|
||||
'<fontconfig>'
|
||||
' <alias><family>SimSun</family><prefer><family>Noto Serif CJK SC</family></prefer></alias>'
|
||||
' <alias><family>NSimSun</family><prefer><family>Noto Serif CJK SC</family></prefer></alias>'
|
||||
' <alias><family>KaiTi</family><prefer><family>Noto Serif CJK SC</family></prefer></alias>'
|
||||
' <alias><family>FangSong</family><prefer><family>Noto Serif CJK SC</family></prefer></alias>'
|
||||
' <alias><family>SimHei</family><prefer><family>Noto Sans CJK SC</family></prefer></alias>'
|
||||
' <alias><family>DengXian</family><prefer><family>Noto Sans CJK SC</family></prefer></alias>'
|
||||
' <alias><family>Microsoft YaHei</family><prefer><family>Noto Sans CJK SC</family></prefer></alias>'
|
||||
'</fontconfig>'
|
||||
> /etc/fonts/local.conf &&
|
||||
fc-cache -f &&
|
||||
mkdir -p /run/sshd && /usr/sbin/sshd &&
|
||||
printf '%s\n' 'cd /app >/dev/null 2>&1 || true' > /etc/profile.d/zz-x-financial-app-dir.sh &&
|
||||
chmod 644 /etc/profile.d/zz-x-financial-app-dir.sh &&
|
||||
touch /root/.bashrc /root/.profile &&
|
||||
if ! grep -qxF 'cd /app >/dev/null 2>&1 || true' /root/.bashrc; then printf '\ncd /app >/dev/null 2>&1 || true\n' >> /root/.bashrc; fi &&
|
||||
if ! grep -qxF 'cd /app >/dev/null 2>&1 || true' /root/.profile; then printf '\ncd /app >/dev/null 2>&1 || true\n' >> /root/.profile; fi &&
|
||||
sed -i 's/\r$//' /app/start.sh /app/web/web_start.sh /app/server/server_start.sh &&
|
||||
chmod +x /app/start.sh /app/web/web_start.sh /app/server/server_start.sh &&
|
||||
cd /app &&
|
||||
./start.sh all
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:${WEB_PORT:-5173}/ >/dev/null || exit 1"]
|
||||
interval: 15s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 180s
|
||||
networks:
|
||||
- financial-internal
|
||||
|
||||
postgres:
|
||||
image: pgvector/pgvector:pg17
|
||||
container_name: x-financial-postgres
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_DB: "${POSTGRES_DB:-x_financial}"
|
||||
POSTGRES_USER: "${POSTGRES_USER:-x_financial}"
|
||||
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:-x_financial}"
|
||||
ports:
|
||||
- "${POSTGRES_HOST_PORT:-55432}:5432"
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U \"$${POSTGRES_USER}\" -d \"$${POSTGRES_DB}\""]
|
||||
interval: 15s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 30s
|
||||
networks:
|
||||
- financial-internal
|
||||
|
||||
qdrant:
|
||||
image: qdrant/qdrant:latest
|
||||
container_name: x-financial-qdrant
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${QDRANT_HTTP_PORT:-6333}:6333"
|
||||
- "${QDRANT_GRPC_PORT:-6334}:6334"
|
||||
volumes:
|
||||
- qdrant-storage:/qdrant/storage
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "bash -lc 'exec 3<>/dev/tcp/127.0.0.1/6333' || exit 1"]
|
||||
interval: 15s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 30s
|
||||
networks:
|
||||
- financial-internal
|
||||
|
||||
onlyoffice:
|
||||
image: onlyoffice/documentserver:latest
|
||||
container_name: x-financial-onlyoffice
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
JWT_ENABLED: "true"
|
||||
JWT_SECRET: "${ONLYOFFICE_JWT_SECRET:-x-financial-onlyoffice-dev-secret}"
|
||||
ports:
|
||||
- "${ONLYOFFICE_PORT:-8082}:80"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -fsS http://127.0.0.1/healthcheck >/dev/null || exit 1"]
|
||||
interval: 15s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 60s
|
||||
networks:
|
||||
- financial-internal
|
||||
|
||||
networks:
|
||||
financial-internal:
|
||||
name: financial-internal
|
||||
|
||||
volumes:
|
||||
postgres-data:
|
||||
qdrant-storage:
|
||||
@@ -3,6 +3,13 @@ services:
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
POSTGRES_HOST: postgres
|
||||
POSTGRES_PORT: "5432"
|
||||
POSTGRES_DB: "${POSTGRES_DB:-x_financial}"
|
||||
POSTGRES_USER: "${POSTGRES_USER:-x_financial}"
|
||||
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:-x_financial}"
|
||||
DATABASE_URL: "postgresql+psycopg://${POSTGRES_USER:-x_financial}:${POSTGRES_PASSWORD:-x_financial}@postgres:5432/${POSTGRES_DB:-x_financial}"
|
||||
|
||||
postgres:
|
||||
image: pgvector/pgvector:pg17
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
services:
|
||||
main:
|
||||
image: x-financial-dev:latest
|
||||
container_name: x-financial-main
|
||||
container_name: local-x-financial-linux
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
onlyoffice:
|
||||
condition: service_started
|
||||
qdrant:
|
||||
condition: service_started
|
||||
environment:
|
||||
WEB_HOST: 0.0.0.0
|
||||
WEB_PORT: "${WEB_PORT:-5173}"
|
||||
SERVER_HOST: 0.0.0.0
|
||||
SERVER_PORT: "${SERVER_PORT:-8000}"
|
||||
SERVER_RELOAD: "${SERVER_RELOAD:-true}"
|
||||
SERVER_VENV_DIR: /tmp/x-financial-server-venv
|
||||
X_FINANCIAL_PREFER_ENV_FILE: "true"
|
||||
ONLYOFFICE_ENABLED: "${ONLYOFFICE_ENABLED:-true}"
|
||||
ONLYOFFICE_PUBLIC_URL: "${ONLYOFFICE_PUBLIC_URL:-http://127.0.0.1:${ONLYOFFICE_PORT:-8082}}"
|
||||
ONLYOFFICE_BACKEND_URL: "http://main:${SERVER_PORT:-8000}"
|
||||
ONLYOFFICE_ENABLED: "${ONLYOFFICE_ENABLED:-false}"
|
||||
ONLYOFFICE_PUBLIC_URL: "${ONLYOFFICE_PUBLIC_URL:-}"
|
||||
ONLYOFFICE_BACKEND_URL: "${ONLYOFFICE_BACKEND_URL:-}"
|
||||
ONLYOFFICE_JWT_SECRET: "${ONLYOFFICE_JWT_SECRET:-x-financial-onlyoffice-dev-secret}"
|
||||
QDRANT_URL: "http://qdrant:6333"
|
||||
QDRANT_URL: "${QDRANT_URL:-}"
|
||||
LIGHTRAG_WORKSPACE: "x_financial_knowledge"
|
||||
ports:
|
||||
- "${WEB_PORT:-5273}:${WEB_PORT:-5273}"
|
||||
- "${WEB_PORT:-5173}:${WEB_PORT:-5173}"
|
||||
- "${SERVER_PORT:-8000}:${SERVER_PORT:-8000}"
|
||||
- "2223:22"
|
||||
volumes:
|
||||
@@ -32,7 +30,7 @@ services:
|
||||
- >
|
||||
apt-get update &&
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends
|
||||
python3 python3-pip python3-venv fontconfig openssh-server poppler-data &&
|
||||
python3 python3-pip python3-venv fontconfig openssh-server poppler-data mupdf-tools &&
|
||||
if ! fc-match 'Noto Sans CJK SC' | grep -qi 'Noto'; then if ! timeout "${CJK_FONT_INSTALL_TIMEOUT_SECONDS:-45}" sh -lc 'DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends fonts-noto-cjk fonts-noto-cjk-extra'; then printf '%s\n' '[WARN] CJK font installation timed out or failed; continuing startup without blocking the app.'; fi; fi &&
|
||||
printf '%s\n'
|
||||
'<?xml version="1.0"?>'
|
||||
@@ -59,7 +57,7 @@ services:
|
||||
cd /app &&
|
||||
./start.sh all
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:${WEB_PORT:-5273}/ >/dev/null || exit 1"]
|
||||
test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:${WEB_PORT:-5173}/ >/dev/null || exit 1"]
|
||||
interval: 15s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
@@ -67,45 +65,6 @@ services:
|
||||
networks:
|
||||
- financial-internal
|
||||
|
||||
qdrant:
|
||||
image: qdrant/qdrant:latest
|
||||
container_name: x-financial-qdrant
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${QDRANT_HTTP_PORT:-6333}:6333"
|
||||
- "${QDRANT_GRPC_PORT:-6334}:6334"
|
||||
volumes:
|
||||
- qdrant-storage:/qdrant/storage
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "bash -lc 'exec 3<>/dev/tcp/127.0.0.1/6333' || exit 1"]
|
||||
interval: 15s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 30s
|
||||
networks:
|
||||
- financial-internal
|
||||
|
||||
onlyoffice:
|
||||
image: onlyoffice/documentserver:latest
|
||||
container_name: x-financial-onlyoffice
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
JWT_ENABLED: "true"
|
||||
JWT_SECRET: "${ONLYOFFICE_JWT_SECRET:-x-financial-onlyoffice-dev-secret}"
|
||||
ports:
|
||||
- "${ONLYOFFICE_PORT:-8082}:80"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -fsS http://127.0.0.1/healthcheck >/dev/null || exit 1"]
|
||||
interval: 15s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 60s
|
||||
networks:
|
||||
- financial-internal
|
||||
|
||||
networks:
|
||||
financial-internal:
|
||||
name: financial-internal
|
||||
|
||||
volumes:
|
||||
qdrant-storage:
|
||||
|
||||
146
docker/README.md
146
docker/README.md
@@ -1,67 +1,127 @@
|
||||
# Docker Compose
|
||||
|
||||
This project currently uses the Vite `__setup/*` middleware during the initial setup flow.
|
||||
Because of that, the Docker deployment keeps the web frontend and FastAPI startup chain in
|
||||
the same main container and runs the existing root `start.sh`.
|
||||
X-Financial 现在按运行依赖分成两层 Docker Compose:
|
||||
|
||||
## Start
|
||||
- `docker-compose.yml`:只启动主应用容器,适合已经有远端 PostgreSQL、ONLYOFFICE 或 Qdrant 的环境。
|
||||
- `docker-compose.full.yml`:启动完整本地开发栈,适合没有外部依赖、希望本机一次性跑齐所有服务的环境。
|
||||
|
||||
主应用容器仍然同时启动 Web 前端和 FastAPI 后端,并复用根目录 `start.sh`。
|
||||
项目根目录会挂载到容器内 `/app`。
|
||||
|
||||
## 轻量启动:只跑主应用
|
||||
|
||||
适合你已经有数据库和 ONLYOFFICE 的情况。
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Open:
|
||||
默认只会启动:
|
||||
|
||||
```text
|
||||
main
|
||||
```
|
||||
|
||||
打开:
|
||||
|
||||
```text
|
||||
http://<your-linux-host>:5273
|
||||
```
|
||||
|
||||
## Container Layout
|
||||
这条路径不会主动拉起本地 PostgreSQL、Qdrant 或 ONLYOFFICE。
|
||||
数据库、ONLYOFFICE 和 Qdrant 地址都从 `.env` 或外部环境变量读取。
|
||||
|
||||
- `main`: web + FastAPI main container
|
||||
- `onlyoffice`: ONLYOFFICE Document Server
|
||||
- `postgres`: PostgreSQL database container
|
||||
|
||||
The project root is mounted directly into the main container:
|
||||
常见外部依赖变量:
|
||||
|
||||
```text
|
||||
.:/app
|
||||
DATABASE_URL
|
||||
POSTGRES_HOST
|
||||
POSTGRES_PORT
|
||||
ONLYOFFICE_ENABLED
|
||||
ONLYOFFICE_PUBLIC_URL
|
||||
ONLYOFFICE_BACKEND_URL
|
||||
QDRANT_URL
|
||||
```
|
||||
|
||||
That means the container reads your existing `.env`, source code, `server/.secrets`, logs,
|
||||
and generated dependency directories directly from the mapped project folder.
|
||||
## 完整启动:本地全栈
|
||||
|
||||
This is a `compose`-only setup. There is no custom `Dockerfile`.
|
||||
The tradeoff is that the `main` container installs the Python runtime packages it needs
|
||||
when it starts.
|
||||
适合没有远端数据库和 ONLYOFFICE 的情况。
|
||||
|
||||
## Persistence
|
||||
```bash
|
||||
docker compose -f docker-compose.full.yml up -d
|
||||
```
|
||||
|
||||
The PostgreSQL data directory is stored in the named volume `postgres_data`.
|
||||
会启动:
|
||||
|
||||
## Notes
|
||||
```text
|
||||
main
|
||||
postgres
|
||||
qdrant
|
||||
onlyoffice
|
||||
```
|
||||
|
||||
- Most configuration should be maintained in the project root `.env`.
|
||||
- The first `docker compose up -d` does not require an existing `.env`; the compose file
|
||||
uses built-in defaults for the PostgreSQL container and the main container database URL.
|
||||
- Docker Compose only overrides a few values that must differ inside containers:
|
||||
- `WEB_HOST=0.0.0.0`
|
||||
- `SERVER_HOST=0.0.0.0`
|
||||
- `POSTGRES_HOST=postgres`
|
||||
- `POSTGRES_PORT=5432`
|
||||
- `DATABASE_URL=...@postgres:...`
|
||||
- PostgreSQL is also published to the host by default as `127.0.0.1:55432`.
|
||||
- ONLYOFFICE is published to the host by default as `127.0.0.1:8082`.
|
||||
- First boot with `SETUP_COMPLETED=false` starts the setup UI only.
|
||||
- After you complete setup in the browser, the Vite setup bridge will start FastAPI in the
|
||||
same container using the saved runtime configuration.
|
||||
- On later restarts, `start.sh` will detect the saved setup state and start both web and
|
||||
server automatically.
|
||||
- If you access the system from another machine, make sure `CORS_ORIGINS` in `.env` includes
|
||||
the frontend address you actually use.
|
||||
- For Navicat or any host-side client, use `127.0.0.1:55432`.
|
||||
- If you need to access ONLYOFFICE from another machine, override `ONLYOFFICE_PUBLIC_URL`
|
||||
so the browser can reach the document server address you actually expose.
|
||||
- For the setup page, using `127.0.0.1` is acceptable in this Docker layout; the internal
|
||||
test bridge will resolve that back to the Docker PostgreSQL service.
|
||||
本地服务端口:
|
||||
|
||||
```text
|
||||
Web: 5273
|
||||
FastAPI: 8000
|
||||
PostgreSQL: 55432 -> 5432
|
||||
Qdrant: 6333 / 6334
|
||||
ONLYOFFICE: 8082
|
||||
SSH: 2223
|
||||
```
|
||||
|
||||
完整栈会把主容器内的数据库地址指向 `postgres:5432`,
|
||||
并把 Qdrant 地址指向 `http://qdrant:6333`。
|
||||
|
||||
ONLYOFFICE 默认使用本机可访问地址:
|
||||
|
||||
```text
|
||||
http://127.0.0.1:8082
|
||||
```
|
||||
|
||||
如果浏览器从另一台机器访问,需要覆盖:
|
||||
|
||||
```bash
|
||||
LOCAL_ONLYOFFICE_PUBLIC_URL=http://<host>:8082 \
|
||||
docker compose -f docker-compose.full.yml up -d
|
||||
```
|
||||
|
||||
如果 ONLYOFFICE 回调后端也需要外部地址,可以同时覆盖:
|
||||
|
||||
```bash
|
||||
LOCAL_ONLYOFFICE_BACKEND_URL=http://<host>:8000 \
|
||||
docker compose -f docker-compose.full.yml up -d
|
||||
```
|
||||
|
||||
## 可选:只额外启动本地 PostgreSQL
|
||||
|
||||
如果只想在轻量主容器旁边补一个本地 PostgreSQL,可以使用覆盖文件:
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.yml -f docker-compose.postgres.yml up -d
|
||||
```
|
||||
|
||||
这会启动:
|
||||
|
||||
```text
|
||||
main
|
||||
postgres
|
||||
```
|
||||
|
||||
## 停止与清理
|
||||
|
||||
停止当前默认轻量栈:
|
||||
|
||||
```bash
|
||||
docker compose down
|
||||
```
|
||||
|
||||
停止完整本地栈:
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.full.yml down
|
||||
```
|
||||
|
||||
如需删除本地数据卷,先确认不再需要其中数据,再手动执行带 `-v` 的清理命令。
|
||||
|
||||
@@ -1,572 +0,0 @@
|
||||
# X-Financial 改进路线图
|
||||
|
||||
> 本文档基于 2026-06-18 对代码库的算法层、业务层、工程层综合评估生成。
|
||||
> 每项改进都附有文件路径佐证,便于后续定位和追踪。
|
||||
> 维护规则:状态变更请在对应章节同步更新;新增改进项追加到对应优先级末尾。
|
||||
|
||||
## 状态约定
|
||||
|
||||
| 标记 | 含义 |
|
||||
|---|---|
|
||||
| ⏳ | 待启动 |
|
||||
| 🔄 | 进行中 |
|
||||
| ✅ | 已完成 |
|
||||
| ⏸️ | 暂缓(需说明原因) |
|
||||
| ❌ | 取消(需说明原因) |
|
||||
|
||||
## 优先级矩阵
|
||||
|
||||
| 优先级 | 编号 | 标题 | 状态 |
|
||||
|---|---|---|---|
|
||||
| 🔴 P0 安全 | B2 | HTTP Header 权限漏洞 | ⏳ |
|
||||
| 🔴 P0 业务核心 | B1 | 审批流转交/加签/撤回/会签 | ⏳ |
|
||||
| 🔴 P0 共识 | B10 | 800 行硬约束破防 | ⏳ |
|
||||
| 🟠 P1 算法 | A1 | 风险评分权重自适应 | ⏳ |
|
||||
| 🟠 P1 算法 | A4 | LLM 票据分类 + 字段置信度 | ⏳ |
|
||||
| 🟠 P1 算法 | A7 | LLM 幻觉检测 | ⏳ |
|
||||
| 🟠 P1 业务 | B6 | 规则覆盖不均衡 | ⏳ |
|
||||
| 🟡 P2 业务 | B3 | 申请/报销拆表 | ⏳ |
|
||||
| 🟡 P2 业务 | B4 | 可配置审批矩阵 | ⏳ |
|
||||
| 🟡 P2 算法 | A2 | 异常检测自适应阈值 | ⏳ |
|
||||
| 🟢 P3 算法 | A3 | 多模型异常检测集成 | ⏳ |
|
||||
| 🟢 P3 算法 | A5 | 票据分类持续学习 | ⏳ |
|
||||
| 🟢 P3 算法 | A6 | Prompt 模板集中管理 | ⏳ |
|
||||
| 🟢 P3 算法 | A8 | 规则冗余建模 | ⏳ |
|
||||
| 🟢 P3 算法 | A9 | 行为画像 fairness 保护 | ⏳ |
|
||||
| 🟢 P3 业务 | B5 | 预算跨期/跨科边界 | ⏳ |
|
||||
| 🟢 P3 业务 | B7 | 审计日志防篡改 | ⏳ |
|
||||
| 🟢 P3 业务 | B8 | 审批 SLA 监控 | ⏳ |
|
||||
| 🟢 P3 业务 | B9 | 支付与凭证对接 | ⏳ |
|
||||
| 🟢 P3 算法 | A10 | 算法模块 800 行拆分 | ⏳ |
|
||||
|
||||
---
|
||||
|
||||
## 一、算法层面改进
|
||||
|
||||
### A1. 风险评分权重自适应调优 ⏳
|
||||
|
||||
**优先级**:🟠 P1
|
||||
|
||||
**证据**:
|
||||
- `server/src/app/algorithem/risk_graph/engine.py:457-465`
|
||||
- `server/src/app/services/risk_rule_scoring.py:16-23`
|
||||
|
||||
**当前实现**:
|
||||
```python
|
||||
risk_score = 0.35*S_rule + 0.25*S_anomaly + 0.20*S_graph + 0.15*S_policy + 0.05*S_history
|
||||
```
|
||||
五维权重和六因子权重均为硬编码常量,无法反映规则有效性差异。
|
||||
|
||||
**问题**:
|
||||
- 已有 `RiskObservationFeedback` 表收集人工反馈,但反馈数据**未反向更新权重**
|
||||
- 不同费用类型(差旅/招待/通信)的合理权重差异大,目前一刀切
|
||||
|
||||
**改进方向**:
|
||||
- 按费用类型分组的权重向量
|
||||
- 定期基于反馈数据做 logistic regression / Bayesian 更新
|
||||
- 权重变更需版本化、可回滚
|
||||
|
||||
**验收标准**:
|
||||
- [ ] 权重从配置/数据库读取,不再硬编码
|
||||
- [ ] 反馈数据能触发权重自动调整
|
||||
- [ ] 不同费用类型可配置独立权重
|
||||
- [ ] 调整过程有日志和效果对比
|
||||
|
||||
---
|
||||
|
||||
### A2. 金额异常检测自适应阈值 ⏳
|
||||
|
||||
**优先级**:🟡 P2
|
||||
|
||||
**证据**:`server/src/app/algorithem/risk_graph/engine.py:221-261`
|
||||
|
||||
**当前实现**:固定分档阈值 `1.0x→0, 1.25x→30, 1.5x→55, 2.0x→75, 3.0x→95`
|
||||
|
||||
**问题**:
|
||||
- 通信费(小额高频)和差旅(大额低频)的"1.5x"含义完全不同
|
||||
- peer p75 在新部门/新费用类型时样本稀疏
|
||||
- 已识别 `peer_baseline_insufficient` 不确定性,但无冷启动方案
|
||||
|
||||
**改进方向**:
|
||||
- 改为自适应分位数(基于历史数据动态计算)
|
||||
- 按 `(费用类型 × 部门层级)` 分组维护基线
|
||||
- 冷启动:全局基线 + 小样本置信度折扣
|
||||
|
||||
**验收标准**:
|
||||
- [ ] 阈值按业务维度分组,不再全局统一
|
||||
- [ ] 新部门/新费用类型有冷启动策略
|
||||
- [ ] 基线样本不足时有降级机制
|
||||
- [ ] 增加单元测试覆盖冷启动场景
|
||||
|
||||
---
|
||||
|
||||
### A3. 多模型异常检测集成策略 ⏳
|
||||
|
||||
**优先级**:🟢 P3
|
||||
|
||||
**证据**:`server/src/app/algorithem/risk_graph/anomaly_models.py`
|
||||
|
||||
**当前实现**:5 个模型独立输出(`robust_statistics / isolation_proxy / local_outlier / temporal_jump / periodic_deviation`),无集成。
|
||||
|
||||
**问题**:
|
||||
- 多模型同时报警时聚合规则未定义
|
||||
- 单模型 vs 多模型共识的严重度差异未体现
|
||||
- 模型间冲突无裁决机制
|
||||
|
||||
**改进方向**:
|
||||
- 引入 `AnomalyEnsembler` 集成层
|
||||
- 输出 `consensus_score` + `model_disagreement_flag`
|
||||
- 高风险图谱评分区分"单点异常"和"多维共识异常"
|
||||
|
||||
**验收标准**:
|
||||
- [ ] 实现集成层并接入 engine.py
|
||||
- [ ] 集成结果包含共识度指标
|
||||
- [ ] 单元测试覆盖各种模型组合情况
|
||||
|
||||
---
|
||||
|
||||
### A4. LLM 票据分类与字段置信度 ⏳
|
||||
|
||||
**优先级**:🟠 P1
|
||||
|
||||
**证据**:
|
||||
- `server/src/app/services/document_intelligence.py:143-153`(`_classify_with_model` 当前 `return None`)
|
||||
- `server/src/app/services/document_intelligence.py:20-37`(字段抽取全正则)
|
||||
|
||||
**问题**:
|
||||
- 规则层单点支撑,非标准票据格式失效
|
||||
- 无字段级置信度评分,无法判断哪些抽取值需要人工复核
|
||||
- LLM 分类合并策略代码已存在但未启用
|
||||
|
||||
**改进方向**:
|
||||
1. 启用 LLM 分类层(合并逻辑可直接复用)
|
||||
2. 字段抽取增加置信度:`{field: {value, confidence, source}}`
|
||||
3. 低置信度字段(< 0.7)自动标记"需人工核对"
|
||||
|
||||
**验收标准**:
|
||||
- [ ] LLM 分类层启用并通过对比测试
|
||||
- [ ] 每个抽取字段附带置信度评分
|
||||
- [ ] 低置信度字段触发人工复核标记
|
||||
- [ ] 提供准确率回归测试集
|
||||
|
||||
---
|
||||
|
||||
### A5. 票据分类持续学习 ⏳
|
||||
|
||||
**优先级**:🟢 P3
|
||||
|
||||
**证据**:`server/src/app/services/document_intelligence_rules.py:120`(`score_bias` 硬编码)
|
||||
|
||||
**问题**:新票种(ETC 电子票、滴滴行程单新模板)需开发改代码才能识别。
|
||||
|
||||
**改进方向**:
|
||||
- 分类规则做成可配置 + 可学习
|
||||
- 管理员上传样本自动更新关键词权重
|
||||
- 基于历史已分类票据做 TF-IDF 训练
|
||||
|
||||
**验收标准**:
|
||||
- [ ] 后台提供分类规则管理界面
|
||||
- [ ] 新票种可通过样本上传识别
|
||||
- [ ] 历史数据可训练关键词权重
|
||||
|
||||
---
|
||||
|
||||
### A6. Prompt 模板集中管理 ⏳
|
||||
|
||||
**优先级**:🟢 P3
|
||||
|
||||
**证据**:
|
||||
- `server/src/app/services/ontology_extraction.py`
|
||||
- `server/src/app/services/ontology_detection.py`
|
||||
- `server/src/app/services/risk_rule_generation.py`
|
||||
- `server/src/app/services/user_agent_response.py`
|
||||
- `server/src/app/services/user_agent_application.py`
|
||||
- `server/src/app/services/user_agent_review_core.py`
|
||||
- `server/src/app/services/knowledge_rag.py:214`(查询重写硬编码在方法内)
|
||||
|
||||
**问题**:
|
||||
- Prompt 散落在 12+ 文件,无版本化、无回滚、无 A/B 测试
|
||||
- 相同意图的 prompt 在不同 service 中重复
|
||||
|
||||
**改进方向**:
|
||||
- 建立 `prompts/` 集中目录 + `PromptRegistry`
|
||||
- 按 `(意图, 版本)` 管理
|
||||
- 支持灰度发布和效果对比
|
||||
|
||||
**验收标准**:
|
||||
- [ ] 所有 prompt 迁移到集中目录
|
||||
- [ ] 支持版本化与回滚
|
||||
- [ ] 提供 A/B 测试接口
|
||||
|
||||
---
|
||||
|
||||
### A7. LLM 幻觉检测与事实校验 ⏳
|
||||
|
||||
**优先级**:🟠 P1
|
||||
|
||||
**证据**:当前系统缺少 LLM 输出的显式幻觉检测。本体解析有 `confidence` 门禁,但生成的解释文本、规则建议、对话回复无校验。
|
||||
|
||||
**问题**:
|
||||
- LLM 可能编造不存在的政策条款、错误金额阈值、虚构审批人
|
||||
- 风险图谱解释文本幻觉会误导审批人
|
||||
- 唯一兜底是 `data_quality_gate`,仅管输入数据质量
|
||||
|
||||
**改进方向**:
|
||||
- 关键输出(金额、政策条款、规则编号)做 grounded check:LLM 输出后用规则引擎反向校验
|
||||
- 对话回复中的具体数字、日期强制引用证据片段(RAG 引用)
|
||||
|
||||
**验收标准**:
|
||||
- [ ] 关键数值字段有反向校验机制
|
||||
- [ ] 对话回复中的事实声明可追溯到证据
|
||||
- [ ] 校验失败时有明确降级策略
|
||||
|
||||
---
|
||||
|
||||
### A8. 规则冗余/相关性建模 ⏳
|
||||
|
||||
**优先级**:🟢 P3
|
||||
|
||||
**证据**:`server/src/app/services/risk_rule_scoring.py`(多规则命中简单求和/max)
|
||||
|
||||
**问题**:相关规则同时命中时分数被夸大。如 `preapproval_absent` 和 `date_outside_trip` 可能高度相关。
|
||||
|
||||
**改进方向**:引入规则相关矩阵,对相关规则命中分数做去冗余折扣。
|
||||
|
||||
**验收标准**:
|
||||
- [ ] 建立规则相关矩阵
|
||||
- [ ] 命中聚合时考虑冗余
|
||||
- [ ] 测试验证去冗余效果
|
||||
|
||||
---
|
||||
|
||||
### A9. 行为画像 fairness 保护 ⏳
|
||||
|
||||
**优先级**:🟢 P3
|
||||
|
||||
**证据**:`server/src/app/algorithem/employee_behavior_profile.py:345`(`evaluate_weighted_profile` / `calculate_review_priority_score`)
|
||||
|
||||
**问题**:行为画像影响审核优先级,若基于受保护属性(性别/年龄)产生系统性偏差,会构成隐性歧视。
|
||||
|
||||
**改进方向**:
|
||||
- 增加 fairness audit 接口(按人群分组统计风险分布)
|
||||
- 评分特征显式排除受保护属性
|
||||
- 定期输出偏差报告
|
||||
|
||||
**验收标准**:
|
||||
- [ ] 评分特征清单明确排除受保护属性
|
||||
- [ ] 提供 fairness audit API
|
||||
- [ ] 定期偏差报告生成
|
||||
|
||||
---
|
||||
|
||||
### A10. 算法模块 800 行拆分 ⏳
|
||||
|
||||
**优先级**:🟢 P3(与 B10 同源,单独追踪算法模块进度)
|
||||
|
||||
**证据**:
|
||||
- `server/src/app/algorithem/employee_behavior_profile_tag_rules.py`: **812 行** 🔴
|
||||
- `server/src/app/algorithem/risk_graph/engine.py`: **794 行** 🟡 临界
|
||||
|
||||
**改进方向**:
|
||||
- `employee_behavior_profile_tag_rules.py` 按标签类别拆分(差旅类 / 招待类 / 办公类)
|
||||
- `engine.py` 的 5 个评分维度(rule/anomaly/graph/policy/history)拆为 5 个独立打分器
|
||||
|
||||
**验收标准**:
|
||||
- [ ] 所有算法文件 ≤ 800 行
|
||||
- [ ] 拆分前后行为等价(单元测试通过)
|
||||
- [ ] 拆分后职责边界清晰
|
||||
|
||||
---
|
||||
|
||||
## 二、业务层面改进
|
||||
|
||||
### B1. 审批流转交/加签/撤回/会签 ⏳
|
||||
|
||||
**优先级**:🔴 P0
|
||||
|
||||
**证据**:
|
||||
- `server/src/app/services/expense_claim_workflow_constants.py`(仅 11 行固定阶段)
|
||||
- `server/src/app/services/expense_claim_approval_flow.py:28`(`approve_claim` 串行硬编码)
|
||||
- 转交/加签/撤回代码中**不存在**
|
||||
|
||||
**问题**:
|
||||
- 费控系统核心能力缺失
|
||||
- 现实中领导出差无法审批是常态
|
||||
- 无并行审批(会签),多人审批只能串行
|
||||
- 审批节点调整需改代码
|
||||
|
||||
**改进方向**:
|
||||
- 引入审批矩阵:`费用类型 × 金额区间 × 部门` → 审批节点列表
|
||||
- 支持节点动作:`{approve, reject, return, transfer, countersign, withdraw, add_approver}`
|
||||
- 短期优先实现"转交"和"撤回"两个最常用动作
|
||||
|
||||
**验收标准**:
|
||||
- [ ] 支持转交(审批人转给他人)
|
||||
- [ ] 支持撤回(提交人在审批中撤回)
|
||||
- [ ] 支持加签(临时增加审批节点)
|
||||
- [ ] 支持会签(多节点并行)
|
||||
- [ ] 审批矩阵可后台配置
|
||||
- [ ] 关键操作有审计日志
|
||||
|
||||
---
|
||||
|
||||
### B2. HTTP Header 权限漏洞修复 ⏳
|
||||
|
||||
**优先级**:🔴 P0(安全)
|
||||
|
||||
**证据**:`server/src/app/api/deps.py:33-213`,通过 `X-Auth-Username / X-Auth-Role-Codes / X-Auth-Is-Admin` 等请求头识别身份。
|
||||
|
||||
**问题**:
|
||||
- **任何人只要在请求头加 `X-Auth-Is-Admin: true` 就能获得管理员权限**
|
||||
- 没有 token、没有签名、没有任何校验
|
||||
- 足以让所有费控规则形同虚设
|
||||
|
||||
**改进方向**:
|
||||
- 引入真正的身份认证(JWT 或 session cookie)
|
||||
- 角色信息从服务端 session/token 获取,**绝不信任客户端传来的角色声明**
|
||||
- 短期方案:前置网关(nginx)剥离这些头并注入真实身份
|
||||
|
||||
**验收标准**:
|
||||
- [ ] 客户端无法通过伪造 Header 越权
|
||||
- [ ] 所有角色信息来自服务端校验
|
||||
- [ ] 现有 API 行为兼容(不破坏调用方)
|
||||
- [ ] 安全测试覆盖权限边界
|
||||
|
||||
---
|
||||
|
||||
### B3. 申请单与报销单拆表 ⏳
|
||||
|
||||
**优先级**:🟡 P2
|
||||
|
||||
**证据**:
|
||||
- `server/src/app/models/financial_record.py`:`ExpenseClaim` 通过 `expense_type` 后缀 + `claim_no` 前缀区分
|
||||
- `server/src/app/models/reimbursement.py`:`ReimbursementRequest` 几乎废弃(service 仅 54 行 CRUD)
|
||||
|
||||
**问题**:
|
||||
- 查询复杂度高,每个查询都要带 `expense_type IN (...)` 过滤
|
||||
- 字段冗余(申请单无发票字段但表里有)
|
||||
- 业务语义混乱("claim"分不清是申请还是报销)
|
||||
- 索引难优化
|
||||
|
||||
**改进方向**(需决策):
|
||||
- 方案 A(保守):保留单表,增加 `claim_kind` 字段(`application` / `reimbursement`)显式区分
|
||||
- 方案 B(彻底):拆分为 `ExpenseApplication` + `ExpenseReimbursement` 两张表,通过 `application_id` 外键关联
|
||||
- **涉及数据迁移,需用户确认方案**
|
||||
|
||||
**验收标准**:
|
||||
- [ ] 方案决策完成
|
||||
- [ ] 数据迁移脚本可重入、可回滚
|
||||
- [ ] 迁移前后数据等价校验
|
||||
- [ ] 现有 API 行为兼容或平滑升级
|
||||
|
||||
---
|
||||
|
||||
### B4. 可配置审批矩阵 ⏳
|
||||
|
||||
**优先级**:🟡 P2
|
||||
|
||||
**证据**:`server/src/app/services/expense_claim_approval_routing.py`(`_APPLICATION_BUDGET_REVIEW_USAGE_THRESHOLD = 90%` 等阈值硬编码)
|
||||
|
||||
**问题**:什么金额走什么审批、什么情况要预算管理者介入,全部硬编码。不同公司/部门差异大,无法运维配置。
|
||||
|
||||
**改进方向**:建立审批矩阵配置表:
|
||||
```
|
||||
approval_matrix(expense_type, amount_range, department_level, risk_level)
|
||||
→ [approver_roles, parallel_or_serial, sla_hours]
|
||||
```
|
||||
管理员后台维护,系统按矩阵动态生成审批流。
|
||||
|
||||
**验收标准**:
|
||||
- [ ] 审批矩阵可后台配置
|
||||
- [ ] 系统按矩阵动态生成审批流
|
||||
- [ ] 配置变更有版本和审计
|
||||
- [ ] 矩阵未命中时有合理默认值
|
||||
|
||||
---
|
||||
|
||||
### B5. 预算管控跨期/跨科边界 ⏳
|
||||
|
||||
**优先级**:🟢 P3
|
||||
|
||||
**证据**:
|
||||
- `server/src/app/services/budget.py`(780 行)
|
||||
- `server/src/app/services/expense_claim_budget_flow.py`(112 行)
|
||||
- 预算占用/释放/核销/转移已实现,但边界场景验证不足
|
||||
|
||||
**潜在漏洞**:
|
||||
- 跨财年结转:去年冻结的预算今年初未释放
|
||||
- 跨期占用:Q1 提交的申请 Q2 才审批,占用的是哪个季度?
|
||||
- 跨科目调剂:差旅预算不够能否临时挪用办公预算?
|
||||
- 无对应单元测试
|
||||
|
||||
**改进方向**:
|
||||
- 增加预算状态周期性对账任务(每日扫描 orphan reservation)
|
||||
- 跨期策略明确化(默认跟随申请提交期,可配置)
|
||||
- 补充跨期/跨科目单元测试
|
||||
|
||||
**验收标准**:
|
||||
- [ ] 跨期/跨科目边界单元测试覆盖
|
||||
- [ ] 周期性对账任务上线
|
||||
- [ ] orphan reservation 自动清理
|
||||
|
||||
---
|
||||
|
||||
### B6. 规则覆盖不均衡补齐 ⏳
|
||||
|
||||
**优先级**:🟠 P1
|
||||
|
||||
**证据**:`server/rules/risk-rules/` 38 条规则分布:
|
||||
- 差旅(travel):13 条
|
||||
- 预算(budget):13 条
|
||||
- 申请(application):5 条
|
||||
- 报销(reimbursement):7 条
|
||||
- 标准(standard):5 条
|
||||
|
||||
**问题**:
|
||||
- **招待费、市场推广、培训费、福利费、软件服务费几乎没有专门规则**
|
||||
- 缺少供应商关联方交易、连号发票重复报销、跨年度重复报销检测
|
||||
- 这些恰是真实费控场景最易出问题的领域
|
||||
|
||||
**改进方向**:
|
||||
1. 招待费规则(参与人数缺失、人均超标、同城招待、节假日招待)
|
||||
2. 供应商风险规则(同一供应商高频、关联方、工商信息异常)
|
||||
3. 重复报销检测(发票号哈希去重、跨期扫描)
|
||||
|
||||
**验收标准**:
|
||||
- [ ] 招待费规则集(≥5 条)
|
||||
- [ ] 供应商风险规则集(≥3 条)
|
||||
- [ ] 重复报销检测规则(≥2 条)
|
||||
- [ ] 每条新规则有对应单元测试
|
||||
|
||||
---
|
||||
|
||||
### B7. 审计日志防篡改 ⏳
|
||||
|
||||
**优先级**:🟢 P3
|
||||
|
||||
**证据**:
|
||||
- `server/src/app/models/audit_log.py`
|
||||
- `server/src/app/services/audit.py`(72 行)
|
||||
- before/after JSON 快照完整,但**无 hash chain 或数字签名**
|
||||
|
||||
**问题**:数据库管理员(或有 DB 写权限的人)可静默篡改审计日志。
|
||||
|
||||
**改进方向**:
|
||||
- 每条日志附加 `prev_hash + current_hash = sha256(prev_hash + payload)`
|
||||
- 定期锚定到外部存证(区块链 / 公证处 / WORM 存储)
|
||||
|
||||
**验收标准**:
|
||||
- [ ] 审计日志实现 hash chain
|
||||
- [ ] 篡改可被检测
|
||||
- [ ] 外部存证机制(至少文档化)
|
||||
|
||||
---
|
||||
|
||||
### B8. 审批 SLA 与时效监控 ⏳
|
||||
|
||||
**优先级**:🟢 P3
|
||||
|
||||
**证据**:审批节点无超时提醒代码。
|
||||
|
||||
**问题**:单据卡在某领导处一周无人管,系统无感知。
|
||||
|
||||
**改进方向**:
|
||||
- 每个审批节点配置 SLA(如 24h/48h)
|
||||
- 后台定时任务扫描超时单据
|
||||
- 自动催办 / 升级到上级 / 转交
|
||||
|
||||
**验收标准**:
|
||||
- [ ] SLA 可配置
|
||||
- [ ] 超时自动催办
|
||||
- [ ] 超时升级机制
|
||||
- [ ] SLA 报表可查
|
||||
|
||||
---
|
||||
|
||||
### B9. 支付与凭证对接 ⏳
|
||||
|
||||
**优先级**:🟢 P3(业务延伸方向)
|
||||
|
||||
**证据**:状态机到 `paid` 就结束,无银企直连、无会计凭证生成。
|
||||
|
||||
**问题**:报销审批通过后仍需财务人工付款、手工录凭证,未形成完整闭环。
|
||||
|
||||
**改进方向**:
|
||||
- 银企直连(用友 / 金蝶 / 远光 API)
|
||||
- 自动生成会计凭证(借:管理费用-差旅,贷:银行存款/应付职工薪酬)
|
||||
|
||||
**验收标准**:
|
||||
- [ ] 至少接入一个财务系统
|
||||
- [ ] 凭证自动生成
|
||||
- [ ] 支付状态回传
|
||||
|
||||
---
|
||||
|
||||
### B10. 800 行硬约束拆分(业务模块) ⏳
|
||||
|
||||
**优先级**:🔴 P0
|
||||
|
||||
**证据**:services/ 下 ≥ 800 行的文件,共 **20 个**:
|
||||
|
||||
| 文件 | 行数 | 超标幅度 |
|
||||
|---|---|---|
|
||||
| `services/user_agent_application.py` | 1451 | +81% |
|
||||
| `services/risk_rule_template_executor.py` | 1164 | +45% |
|
||||
| `services/expense_claim_draft_flow.py` | 1064 | +33% |
|
||||
| `services/expense_claims.py` | 1042 | +30% |
|
||||
| `services/receipt_folder.py` | 1034 | +29% |
|
||||
| `services/steward_planner.py` | 935 | +17% |
|
||||
| `api/v1/endpoints/agent_assets.py` | 925 | +16% |
|
||||
| `services/orchestrator_execution.py` | 900 | +12.5% |
|
||||
| `services/finance_dashboard.py` | 884 | +10.5% |
|
||||
| `services/knowledge_rag.py` | 877 | +9.6% |
|
||||
| `services/settings.py` | 873 | +9.1% |
|
||||
| `services/agent_assets.py` | 856 | +7% |
|
||||
| `services/employee.py` | 850 | +6.25% |
|
||||
| `services/employee_behavior_profile_service.py` | 823 | +2.9% |
|
||||
| `services/risk_rule_generation.py` | 821 | +2.6% |
|
||||
| `services/agent_foundation_asset_topup.py` | 809 | +1.1% |
|
||||
| `services/ontology_extraction.py` | 808 | +1% |
|
||||
| `services/demo_company_simulation_seed.py` | 805 | +0.6% |
|
||||
| `services/knowledge.py` | 800 | 临界 |
|
||||
| 另约 20 个文件在 700-800 行区间 | | 🟡 |
|
||||
|
||||
**前端超大文件**:
|
||||
|
||||
| 文件 | 行数 |
|
||||
|---|---|
|
||||
| `web/src/views/scripts/TravelReimbursementCreateView.js` | 4066 🔴🔴 |
|
||||
| `web/src/views/scripts/TravelRequestDetailView.js` | 2861 🔴🔴 |
|
||||
| `web/src/views/scripts/useTravelReimbursementSubmitComposer.js` | 2173 🔴🔴 |
|
||||
| `web/src/composables/useRequests.js` | 1799 🔴 |
|
||||
| `web/src/views/scripts/travelReimbursementReviewModel.js` | 1662 🔴 |
|
||||
| 多个 `.vue` 文件 | 800-1130 🔴 |
|
||||
|
||||
**改进方向**:
|
||||
- 按 AGENTS.md 既定的拆分原则(编排 / 状态 / 持久化 / 权限 / 文件存储 / OCR / 规则审核 / 响应构建 / 序列化)逐个拆
|
||||
- 优先 Top 5:`user_agent_application` / `risk_rule_template_executor` / `expense_claim_draft_flow` / `expense_claims` / `receipt_folder`
|
||||
- 每次拆分配套定向测试
|
||||
|
||||
**验收标准**:
|
||||
- [ ] 所有类/文件 ≤ 800 行
|
||||
- [ ] 拆分前后行为等价(测试通过)
|
||||
- [ ] 拆分后职责边界清晰
|
||||
- [ ] CI 中加入行数检查(防止回潮)
|
||||
|
||||
---
|
||||
|
||||
## 三、推进原则
|
||||
|
||||
1. **P0 优先**:B2(安全)、B1(核心能力)、B10(共识)必须先行。
|
||||
2. **算法优化在 P0 落地后做**:再准的算法也会被权限漏洞和流程缺失抵消。
|
||||
3. **小步快跑**:每项改进拆成可独立验证的子任务,配套测试。
|
||||
4. **不破坏既有协议**:对外 API 尽量稳定,内部实现先拆。
|
||||
5. **800 行约束**:所有改动前后检查受影响类行数,CI 加入行数门禁。
|
||||
|
||||
---
|
||||
|
||||
## 四、变更日志
|
||||
|
||||
| 日期 | 变更 | 操作人 |
|
||||
|---|---|---|
|
||||
| 2026-06-18 | 路线图初始版本,基于代码库全量评估生成 | Sisyphus |
|
||||
@@ -1,177 +0,0 @@
|
||||
# Steward Application Reimbursement State Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Build a persistent ontology-bound steward state for travel application and travel reimbursement flows.
|
||||
|
||||
**Architecture:** Keep the existing steward planning UI and assistant delegation flow. Add a backend state layer that stores `steward_state` in `AgentConversation.state_json`, merges LLM/rule output as patches, and rejects fields outside the ontology registry before downstream services consume them.
|
||||
|
||||
**Tech Stack:** FastAPI, SQLAlchemy JSON state, Pydantic schemas, pytest in Docker `x-financial-main:/app`, Vue/Vite frontend.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Backend State Contract
|
||||
|
||||
**Files:**
|
||||
- Modify: `server/src/app/schemas/steward.py`
|
||||
- Create: `server/src/app/services/steward_flow_state.py`
|
||||
- Test: `server/tests/test_steward_flow_state.py`
|
||||
|
||||
- [ ] **Step 1: Write failing tests**
|
||||
|
||||
```python
|
||||
def test_state_merge_keeps_application_and_reimbursement_flows():
|
||||
service = StewardFlowStateService()
|
||||
state = service.merge_state(
|
||||
{},
|
||||
StewardFlowStatePatch(
|
||||
active_flow="travel_application",
|
||||
flow_id="travel_application",
|
||||
intent="travel_application_create",
|
||||
fields={"expense_type": "travel", "location": "上海"},
|
||||
),
|
||||
)
|
||||
state = service.merge_state(
|
||||
state,
|
||||
StewardFlowStatePatch(
|
||||
active_flow="travel_reimbursement",
|
||||
flow_id="travel_reimbursement",
|
||||
intent="travel_reimbursement_draft",
|
||||
fields={"amount": "708", "invoice_no": "NO-1"},
|
||||
),
|
||||
)
|
||||
|
||||
assert state["flows"]["travel_application"]["fields"]["location"] == "上海"
|
||||
assert state["flows"]["travel_reimbursement"]["fields"]["amount"] == "708"
|
||||
```
|
||||
|
||||
```python
|
||||
def test_state_merge_filters_non_ontology_fields():
|
||||
service = StewardFlowStateService()
|
||||
state = service.merge_state(
|
||||
{},
|
||||
StewardFlowStatePatch(
|
||||
active_flow="travel_application",
|
||||
flow_id="travel_application",
|
||||
intent="travel_application_create",
|
||||
fields={"location": "上海", "invented_field": "x"},
|
||||
),
|
||||
)
|
||||
|
||||
assert state["flows"]["travel_application"]["fields"] == {"location": "上海"}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run red tests**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
docker exec -w /app -e SERVER_VENV_DIR=/tmp/x-financial-server-venv x-financial-main /tmp/x-financial-server-venv/bin/pytest -q server/tests/test_steward_flow_state.py
|
||||
```
|
||||
|
||||
Expected: fail because `steward_flow_state.py` does not exist.
|
||||
|
||||
- [ ] **Step 3: Implement minimal state service**
|
||||
|
||||
Create `StewardFlowStatePatch`, `StewardFlowStateService.merge_state`, ontology field filtering, and event append logic.
|
||||
|
||||
- [ ] **Step 4: Run green tests**
|
||||
|
||||
Run the same pytest command and expect pass.
|
||||
|
||||
### Task 2: Steward Plan Persistence
|
||||
|
||||
**Files:**
|
||||
- Modify: `server/src/app/schemas/steward.py`
|
||||
- Modify: `server/src/app/api/v1/endpoints/steward.py`
|
||||
- Modify: `server/src/app/services/agent_conversations.py`
|
||||
- Test: `server/tests/test_steward_planner.py`
|
||||
|
||||
- [ ] **Step 1: Write failing API/service test**
|
||||
|
||||
Add a test proving `/steward/plans` response contains `conversation_id` and `steward_state` when `context_json.session_type = steward`, and the state contains two flows when the input contains one application and one reimbursement task.
|
||||
|
||||
- [ ] **Step 2: Run red test**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
docker exec -w /app -e SERVER_VENV_DIR=/tmp/x-financial-server-venv x-financial-main /tmp/x-financial-server-venv/bin/pytest -q server/tests/test_steward_planner.py
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Implement conversation state persistence**
|
||||
|
||||
Add `conversation_id` and `steward_state` fields to response schemas, persist state through `AgentConversationService`, and merge planner tasks into `steward_state`.
|
||||
|
||||
- [ ] **Step 4: Run green test**
|
||||
|
||||
Run the same pytest command and expect pass.
|
||||
|
||||
### Task 3: Runtime Decision Reads Persistent State
|
||||
|
||||
**Files:**
|
||||
- Modify: `server/src/app/services/steward_runtime_decision_agent.py`
|
||||
- Test: `server/tests/test_steward_runtime_decision_agent.py`
|
||||
|
||||
- [ ] **Step 1: Write failing test**
|
||||
|
||||
Add a test proving runtime decision uses `context_json.conversation_state.steward_state` when `runtime_state` is empty.
|
||||
|
||||
- [ ] **Step 2: Implement minimal fallback hydration**
|
||||
|
||||
Normalize runtime state by merging request runtime state with persisted steward state.
|
||||
|
||||
- [ ] **Step 3: Run green test**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
docker exec -w /app -e SERVER_VENV_DIR=/tmp/x-financial-server-venv x-financial-main /tmp/x-financial-server-venv/bin/pytest -q server/tests/test_steward_runtime_decision_agent.py
|
||||
```
|
||||
|
||||
### Task 4: Frontend State Carry
|
||||
|
||||
**Files:**
|
||||
- Modify: `web/src/views/scripts/stewardPlanModel.js`
|
||||
- Modify: `web/src/views/scripts/TravelReimbursementCreateView.js`
|
||||
- Modify: `web/src/views/scripts/useTravelReimbursementSessionState.js`
|
||||
|
||||
- [ ] **Step 1: Preserve steward state from backend responses**
|
||||
|
||||
Normalize `conversation_id` and `steward_state` from plan/runtime responses into the local session model.
|
||||
|
||||
- [ ] **Step 2: Send steward state in later requests**
|
||||
|
||||
Include the current `steward_state` under `context_json` for plan and runtime decision calls.
|
||||
|
||||
- [ ] **Step 3: Build frontend**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
docker exec -w /app/web x-financial-main npm run build
|
||||
```
|
||||
|
||||
Expected: build succeeds.
|
||||
|
||||
### Task 5: Final Verification
|
||||
|
||||
- [ ] **Step 1: Run backend steward tests**
|
||||
|
||||
```bash
|
||||
docker exec -w /app -e SERVER_VENV_DIR=/tmp/x-financial-server-venv x-financial-main /tmp/x-financial-server-venv/bin/pytest -q server/tests/test_steward_flow_state.py server/tests/test_steward_planner.py server/tests/test_steward_runtime_decision_agent.py server/tests/test_steward_slot_decision_agent.py
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run frontend build**
|
||||
|
||||
```bash
|
||||
docker exec -w /app/web x-financial-main npm run build
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Report workspace status**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
git status --short
|
||||
```
|
||||
@@ -0,0 +1,32 @@
|
||||
# 附件自动关联后台任务实施计划
|
||||
|
||||
## 目标
|
||||
|
||||
把小财管家 AI 模式里的附件关联从前端会话内存任务改为后端可查询后台任务,保证用户退出或刷新当前会话后,附件关联仍能继续完成并可恢复状态。
|
||||
|
||||
## 执行清单
|
||||
|
||||
- [x] 定位当前断链根因:前端依赖 `File` 对象和内存 `Map`。
|
||||
- [x] 确认票据夹已有 `receipt_id`、源文件和关联状态能力。
|
||||
- [x] 落开发方案文档。
|
||||
- [x] 实现后端任务 schema 和内存任务池。
|
||||
- [x] 实现后端任务 API。
|
||||
- [x] 实现后端票据夹源文件归集到报销单明细。
|
||||
- [x] 增加后端测试。
|
||||
- [x] 实现前端任务创建、轮询和恢复。
|
||||
- [x] 增加前端测试断言。
|
||||
- [x] 执行容器后端定向测试。
|
||||
- [x] 执行前端定向测试和构建。
|
||||
|
||||
## 验证结果
|
||||
|
||||
- 后端定向测试:`6 passed`
|
||||
- 前端定向测试:`12 passed`
|
||||
- 前端构建:通过,保留既有 chunk size warning。
|
||||
- 运行时检查:新任务查询路由已加载,未知任务返回“附件关联任务不存在或已失效。”
|
||||
|
||||
## 关键决策
|
||||
|
||||
- 第一版使用后端内存任务池和 FastAPI `BackgroundTasks`,解决前端会话断链。
|
||||
- 第一版不新增数据库任务表,服务重启后的任务恢复作为后续增强。
|
||||
- 前端消息只保存 `job_id`、状态和票据引用,不再保存附件原件。
|
||||
@@ -0,0 +1,215 @@
|
||||
# AI 工作台统一意图识别框架设计
|
||||
|
||||
## 背景
|
||||
|
||||
AI 工作台当前的输入识别分散在多个前端 flow 中:申请预览、报销草稿、单据查询、草稿删除提示各自判断输入。这样的结构能快速修复单点问题,但会让“删除 3 天前的草稿”“审核合规没有风险的申请”这类组合型请求继续变成关键词补丁。
|
||||
|
||||
本设计把自然语言输入先统一解析成结构化 `IntentFrame`,再根据目标是否明确、安全等级和业务边界决定下一步动作。
|
||||
|
||||
## 目标
|
||||
|
||||
- 让工作台所有自然语言输入先进入统一意图框架,不再在各业务 flow 中散落判断。
|
||||
- 支持动作、对象、筛选条件、上下文指代、安全等级的组合识别。
|
||||
- 对删除、审核、驳回等高风险动作固定走“筛选候选 + 详情确认”,禁止自然语言直接执行。
|
||||
- 保留当前会话内的快速指代能力,例如“删除刚才那个草稿”能定位最近创建的草稿。
|
||||
- 对带筛选条件的请求,例如“删除 3 天前的草稿”“审核无风险申请”,先展示思考过程和候选列表。
|
||||
|
||||
## 非目标
|
||||
|
||||
- 第一版不引入 LangGraph,也不把前端本地识别迁到后端状态机。
|
||||
- 第一版不做自然语言直接批量删除、批量审核或批量驳回。
|
||||
- 第一版不改后端审批、删除接口的权限模型。
|
||||
- 第一版不重写现有申请预览和报销草稿流程,只把入口识别前置统一。
|
||||
|
||||
## 总体架构
|
||||
|
||||
统一意图识别分三层:
|
||||
|
||||
1. `IntentFrame Parser`:把用户输入解析为结构化意图。
|
||||
2. `Target Resolver`:结合当前会话、最近动作和筛选条件,判断目标是否唯一。
|
||||
3. `Action Policy`:根据动作风险决定直接查询、展示候选、要求澄清或阻断。
|
||||
|
||||
输入链路应调整为:
|
||||
|
||||
```text
|
||||
用户输入
|
||||
-> IntentFrame Parser
|
||||
-> Action Policy
|
||||
-> Target Resolver
|
||||
-> 业务 flow
|
||||
- 查询候选
|
||||
- 打开详情确认
|
||||
- 进入申请/报销流程
|
||||
- 要求补充条件
|
||||
```
|
||||
|
||||
## IntentFrame 数据结构
|
||||
|
||||
```js
|
||||
{
|
||||
action: 'query' | 'delete' | 'approve' | 'reject' | 'create' | 'update' | 'ask_policy',
|
||||
objectType: 'draft' | 'application' | 'reimbursement' | 'approval_task' | 'receipt' | 'document',
|
||||
filters: {
|
||||
timeRange: null,
|
||||
status: null,
|
||||
risk: null,
|
||||
documentType: null,
|
||||
amount: null,
|
||||
keyword: null
|
||||
},
|
||||
targetMode: 'current_context' | 'filtered_candidates' | 'ambiguous',
|
||||
safetyLevel: 'read_only' | 'confirm_required' | 'blocked',
|
||||
confidence: 0,
|
||||
normalizedQuery: ''
|
||||
}
|
||||
```
|
||||
|
||||
### 字段含义
|
||||
|
||||
- `action` 表示用户想做什么,例如查、删、审核、驳回、创建或咨询规则。
|
||||
- `objectType` 表示动作对象,例如草稿、申请单、报销单、待审任务或票据。
|
||||
- `filters` 表示筛选条件,必须可以复用到单据查询 flow。
|
||||
- `targetMode` 表示目标定位方式:
|
||||
- `current_context`:明确指向当前会话最近对象。
|
||||
- `filtered_candidates`:需要查询候选列表。
|
||||
- `ambiguous`:条件不足,需要澄清。
|
||||
- `safetyLevel` 表示动作安全级别:
|
||||
- `read_only`:可直接查询或解释。
|
||||
- `confirm_required`:只展示候选或详情入口,不直接执行。
|
||||
- `blocked`:存在批量破坏性风险或越权风险,必须阻断。
|
||||
- `normalizedQuery` 是给现有查询 flow 使用的可读查询句,例如“我的 3 天前草稿单据”。
|
||||
|
||||
## 识别策略
|
||||
|
||||
### 动作识别
|
||||
|
||||
- 查询:查、看、列出、有哪些、找一下。
|
||||
- 删除:删除、删掉、移除、作废、撤销。
|
||||
- 审核:审核、审批、处理待办、去审批。
|
||||
- 驳回:驳回、退回、拒绝。
|
||||
- 创建:新建、发起、申请、我要报销。
|
||||
- 更新:补充、修改、改成、填入。
|
||||
- 规则咨询:怎么走、能不能、规则、制度、政策、标准。
|
||||
|
||||
### 对象识别
|
||||
|
||||
- 草稿:草稿、未提交、刚才保存的单据。
|
||||
- 申请单:申请、申请单、出差申请、费用申请。
|
||||
- 报销单:报销、报销单、费用报销。
|
||||
- 待审任务:待办、待我审核、待审批、审核单。
|
||||
- 票据:发票、票据、附件、图片。
|
||||
|
||||
### 筛选条件识别
|
||||
|
||||
- 时间:今天、昨天、3 天前、近 7 天、上周、本月、具体日期、日期范围。
|
||||
- 状态:草稿、审批中、已通过、已驳回、待补充。
|
||||
- 风险:无风险、低风险、中风险、高风险、合规、异常、超标。
|
||||
- 金额:超过 1000、500 以下、100 到 300。
|
||||
- 关键词:地点、事由、人员、部门、单号等自由文本。
|
||||
|
||||
## 目标解析规则
|
||||
|
||||
### 当前上下文直达
|
||||
|
||||
当用户使用“刚才那个”“当前”“这个”“上面那个”这类指代,并且当前会话中能找到最近的可操作对象时,`targetMode` 为 `current_context`。
|
||||
|
||||
例子:
|
||||
|
||||
- “删除刚才那个草稿”
|
||||
- “打开这个申请单”
|
||||
- “继续刚才的报销草稿”
|
||||
|
||||
即便目标唯一,删除、审核、驳回仍然只打开详情页或确认入口。
|
||||
|
||||
### 筛选候选
|
||||
|
||||
当用户输入包含时间、风险、金额、状态、类型等筛选条件时,`targetMode` 必须为 `filtered_candidates`。
|
||||
|
||||
例子:
|
||||
|
||||
- “删除 3 天前的草稿”
|
||||
- “审核合规没有风险的申请”
|
||||
- “找一下上海相关的低风险待审申请”
|
||||
|
||||
这类请求必须进入单据查询 flow,展示候选结果,不能直接套最近草稿。
|
||||
|
||||
### 条件不足澄清
|
||||
|
||||
当动作高风险但目标既不唯一,也没有足够筛选条件时,`targetMode` 为 `ambiguous`。
|
||||
|
||||
例子:
|
||||
|
||||
- “把草稿删了”
|
||||
- “帮我审核一下”
|
||||
- “退回这个单”
|
||||
|
||||
系统应提示用户选择候选或补充条件。
|
||||
|
||||
## Action Policy
|
||||
|
||||
| 动作 | 安全等级 | 第一版行为 |
|
||||
| --- | --- | --- |
|
||||
| 查询 | `read_only` | 直接查询并展示结果 |
|
||||
| 规则咨询 | `read_only` | 走政策/规则解释,不进入单据查询 |
|
||||
| 删除 | `confirm_required` | 展示候选或打开详情页确认,不直接删除 |
|
||||
| 审核通过 | `confirm_required` | 展示待审候选或打开审核详情,不直接通过 |
|
||||
| 驳回/退回 | `confirm_required` | 展示待审候选或打开审核详情,不直接驳回 |
|
||||
| 批量删除/批量审核 | `blocked` | 阻断并要求用户选择具体单据 |
|
||||
|
||||
## 用户可见思考过程
|
||||
|
||||
筛选型命令必须复用现有查询 thinking events,并补充动作意图说明:
|
||||
|
||||
1. 解析自然语言动作和筛选条件。
|
||||
2. 判断操作风险和目标定位方式。
|
||||
3. 查询业务单据接口。
|
||||
4. 按条件组合筛选候选。
|
||||
5. 展示候选卡片和下一步入口。
|
||||
|
||||
示例:
|
||||
|
||||
```text
|
||||
解析:识别到“删除”是高风险动作,对象是“草稿”,时间条件是“3 天前”。
|
||||
策略:不会直接删除,将先查询我的草稿候选。
|
||||
结果:命中 2 张草稿,请打开详情页确认删除目标。
|
||||
```
|
||||
|
||||
## 文件边界
|
||||
|
||||
- 新增 `workbenchIntentFrameModel.js`:负责解析用户输入到 `IntentFrame`。
|
||||
- 新增 `workbenchIntentActionPolicy.js`:负责动作安全策略和下一步路由判断。
|
||||
- 调整 `workbenchAiCommandIntentModel.js`:保留会话内最近草稿解析,但不再单独拥有顶层意图判断。
|
||||
- 调整 `useWorkbenchAiCommandIntents.js`:基于 `IntentFrame` 分发到当前上下文直达或候选查询。
|
||||
- 调整 `aiDocumentQueryIntent.js` 和 `aiDocumentQueryModel.js`:补充风险筛选、相对日期和筛选摘要。
|
||||
- 补充前端测试,覆盖组合型输入和安全边界。
|
||||
|
||||
## 迁移步骤
|
||||
|
||||
1. 先为 `IntentFrame` 解析补测试:
|
||||
- “删除刚才那个草稿”解析为 `delete + draft + current_context + confirm_required`。
|
||||
- “删除 3 天前的草稿”解析为 `delete + draft + filtered_candidates + confirm_required`。
|
||||
- “审核合规没有风险的申请”解析为 `approve + application + risk:none + filtered_candidates + confirm_required`。
|
||||
- “审批规则怎么走”解析为 `ask_policy`,不进入单据查询。
|
||||
2. 实现 `workbenchIntentFrameModel.js`,不接入 UI。
|
||||
3. 实现风险和相对日期筛选能力,让查询 flow 能承接筛选型命令。
|
||||
4. 接入 `useWorkbenchAiCommandIntents.js`:
|
||||
- 当前上下文直达:生成详情确认入口。
|
||||
- 筛选候选:调用 `handleAiDocumentQueryIntent(normalizedQuery, pendingMessage)`。
|
||||
- 条件不足:提示补充条件或查询可选候选。
|
||||
5. 移除或降级旧的散落正则,让顶层输入先走统一框架。
|
||||
6. 跑定向测试、相邻测试、前端构建和 5173 工作台烟测。
|
||||
|
||||
## 验收标准
|
||||
|
||||
- 输入“删除刚才那个草稿”时,系统定位当前会话最近草稿,并只打开详情确认入口。
|
||||
- 输入“删除 3 天前的草稿”时,系统展示筛选思考过程和草稿候选,不使用最近草稿快捷路径。
|
||||
- 输入“审核合规没有风险的申请”时,系统查询待我审核申请,并筛选无风险候选。
|
||||
- 输入“审批规则怎么走”时,不进入单据查询。
|
||||
- 删除、审核、驳回均不通过自然语言直接执行最终动作。
|
||||
- 新增/调整测试全部通过,前端构建通过,`git diff --check` 无空白错误。
|
||||
|
||||
## 后续演进
|
||||
|
||||
- 当后端 steward planner 能稳定输出同等 `IntentFrame` 时,可以把 Parser 层迁到后端,前端只保留策略兜底。
|
||||
- 如果需要跨会话目标记忆,可把最近创建/保存草稿写入会话快照,并附带过期时间和用户确认边界。
|
||||
- 如果未来引入 LangGraph,应把它用于多步状态编排,而不是替代 `IntentFrame` schema 本身。
|
||||
@@ -0,0 +1,12 @@
|
||||
# AI 工作台审核单二轮审核命令丢失候选上下文
|
||||
|
||||
日期:2026-06-25
|
||||
文档路径:document/development/2026-06-25/dev-logs/bugs/ai-approval-followup-context.md
|
||||
|
||||
## 修复记录
|
||||
- 16:24:记录 bug 修复:AI 工作台审核单二轮审核命令丢失候选上下文。(bug-log:8a2ae6eb)
|
||||
- Git 提交检查:fetch 失败:fatal: unable to access 'https://www.caoxiaozhu.com:13002/YG-Soft/X-Financial.git/': LibreSSL SSL_connect: SSL_ERROR_SYSCALL in connection to www.caoxiaozhu.com:13002;upstream `origin/main`;upstream 新提交:未发现;本地 ahead 提交:8a2ae6eb (HEAD -> main) fix(server): gate_classify 复用 _classify_irrelevant_input 修复 off_topic 误杀;992cf71f refactor(server): Phase 1 图拓扑重构 - LangGraph 成为唯一编排者;54356ba8 refactor(server): scene 注册表骨架 + 统一门控管道设计文档;e9d7c56d feat(server): 会话上下文保留(LLM 历史 + 确定性兜底双保险)。
|
||||
- 修改:`workbenchAiCommandIntentModel.js` 新增待审单据候选上下文解析与二轮审批命令提示;`useWorkbenchAiCommandIntents.js` 在 `query_candidates` 前优先复用上一轮待审候选;`workbench-ai-command-intent-model.test.mjs` 覆盖“我有哪些审核单”后继续说“审核通过”的候选承接。
|
||||
- 操作:按 TDD 先补失败用例,再实现最小修复;执行 `tools/agent-change-log/update_change_log.py --kind bug` 创建当天 bug 记录,并手动补全真实修复细节。
|
||||
- 验证:`node --test web/tests/workbench-ai-command-intent-model.test.mjs` 通过;`node --test web/tests/workbench-ai-command-intent-model.test.mjs web/tests/workbench-intent-frame-model.test.mjs web/tests/ai-document-query-model.test.mjs` 通过,31 项前端相关测试全部通过。
|
||||
- 影响:用户在 AI 工作台先查询待审/审核单后,再说“请帮我审核通过”或类似审批命令时,系统会接上刚才候选并要求进入详情确认,不会把二轮命令当成孤立查询或静默失智;仍保留高风险审批动作不直接执行的安全边界。
|
||||
@@ -0,0 +1,12 @@
|
||||
# AI模式企业主题光球出现矩形闪动边框
|
||||
|
||||
日期:2026-06-25
|
||||
文档路径:document/development/2026-06-25/dev-logs/bugs/ai-enterprise-orb-frame.md
|
||||
|
||||
## 修复记录
|
||||
- 15:02:记录 bug 修复:AI模式企业主题光球出现矩形闪动边框。(bug-log:2ebc2756)
|
||||
- Git 提交检查:15:01 执行 `git fetch --all --prune` 成功;upstream `origin/main`;upstream 新提交:未发现;本地 ahead 提交:未发现。
|
||||
- 修改:`personal-workbench-ai-mode.css`,把企业主题下 `.workbench-ai-orb` 从白底圆角矩形容器改回透明圆形承载,移除边框与阴影;`settings-theme-section.test.mjs` 增加断言锁定 `border: 0`、`border-radius: 50%`、`background: transparent`、`box-shadow: none`。
|
||||
- 操作:复现时确认企业主题覆盖块把光球容器设置为 `border-radius: 18px`、白底、阴影,导致 GIF 光球外层出现明显闪动方框;修复后用浏览器读取已加载 CSSOM,确认企业主题规则已变为透明圆形版本。
|
||||
- 验证:`node web/tests/settings-theme-section.test.mjs`、`node web/tests/settings-llm-section.test.mjs`、`node web/tests/settings-rendering-section.test.mjs`、`git diff --check`、`npm --prefix web run build` 均通过;本地工作台页面可加载,光球元素存在,页面无 error,浏览器 CSSOM 中企业主题光球规则为 `border: 0px`、`border-radius: 50%`、`background: transparent`、`box-shadow: none`。
|
||||
- 影响:企业沉稳主题下 AI 模式欢迎区光球不再显示矩形闪动边框,动感/专业智能主题保持原有光球表现。
|
||||
@@ -0,0 +1,27 @@
|
||||
# 申请详情退回/草稿状态修改申请卡壳
|
||||
|
||||
日期:2026-06-25
|
||||
文档路径:document/development/2026-06-25/dev-logs/bugs/application-detail-edit-returned-draft.md
|
||||
|
||||
## 修复记录
|
||||
- 16:40:记录 bug 修复:申请详情退回/草稿状态修改申请卡壳。(bug-log:8a2ae6eb)
|
||||
- Git 提交检查:fetch 失败:fatal: unable to access 'https://www.caoxiaozhu.com:13002/YG-Soft/X-Financial.git/': LibreSSL SSL_connect: SSL_ERROR_SYSCALL in connection to www.caoxiaozhu.com:13002;upstream `origin/main`;upstream 新提交:未发现;本地 ahead 提交:8a2ae6eb (HEAD -> main) fix(server): gate_classify 复用 _classify_irrelevant_input 修复 off_topic 误杀;992cf71f refactor(server): Phase 1 图拓扑重构 - LangGraph 成为唯一编排者;54356ba8 refactor(server): scene 注册表骨架 + 统一门控管道设计文档;e9d7c56d feat(server): 会话上下文保留(LLM 历史 + 确定性兜底双保险)。
|
||||
- 修改:`travelRequestDetailSetup.js` 和 `TravelRequestDetailView.vue` 将“修改申请”入口从仅退回态放宽为申请单草稿/退回归一后的可编辑态,仍要求当前用户是申请人;打开助手时补齐原申请 `draftPayload`、`applicationEditMode` 和可编辑字段白名单。
|
||||
- 修改:`useAppShell.js`、`AppShellRouteView.vue`、`TravelReimbursementCreateView.js`、`useTravelReimbursementCreateViewLifecycle.js` 贯通 `initialDraftPayload`,让申请详情带出的核对表首条消息保留原 `claim_id`,保存草稿/直接提交时走已有 `application_edit_claim_id` 更新链路,不再新建或卡在无上下文状态。
|
||||
- 修改:`expenseApplicationPreview.js` 支持 `editableFields`,在修改申请场景只开放事由、时间、地点、出行方式;`aiApplicationPreviewActions.js` 将白名单写入 `application_editable_fields`,便于后续服务端字段级约束。
|
||||
- 操作:manual 触发 `tools/agent-change-log/update_change_log.py --kind bug` 生成日志后补充真实修复记录;未修改后端接口逻辑,因为 `user_agent_application.py` 已允许 `draft/returned/supplement` 申请按 `application_edit_claim_id` 更新。
|
||||
- 验证:`node --test web/tests/travel-request-detail-risk-advice.test.mjs` 通过;`node --test --test-name-pattern "application edit prefill opens assistant without auto submit" web/tests/app-shell-financial-assistant-entry.test.mjs` 通过;`node --test --test-name-pattern "application edit preview only allows reason time location and transport changes" web/tests/expense-application-fast-preview.test.mjs` 通过;`node web/tests/ai-application-preview-actions.test.mjs` 通过;`git diff --check` 通过;`npm --prefix web run build` 通过。全量跑 `app-shell-financial-assistant-entry.test.mjs` 与 `expense-application-fast-preview.test.mjs` 时仍有既有结构断言漂移,失败点与本次修改无关。
|
||||
- 影响:用户在申请详情里看到草稿和退回申请都能继续修改;修改面被限制在事由、时间、地点、出行方式,其他职级、负责人、费用标准和金额仍由原单或规则测算带入。
|
||||
- 16:51:补充修复:取消底部“修改申请”按钮,改为申请详情格子内联铅笔编辑。
|
||||
- Git 提交检查:fetch 仍失败:fatal: unable to access 'https://www.caoxiaozhu.com:13002/YG-Soft/X-Financial.git/': LibreSSL SSL_connect: SSL_ERROR_SYSCALL in connection to www.caoxiaozhu.com:13002;upstream `origin/main`;upstream 新提交:未发现;本地 ahead 提交仍为 8a2ae6eb、992cf71f、54356ba8、e9d7c56d。
|
||||
- 修改:`TravelRequestDetailView.vue` 移除底部“修改申请”按钮,在申请详情事实格子的值旁显示小铅笔;点击后根据字段类型切换为日期框、下拉框或文本输入,并提供保存/取消按钮。
|
||||
- 修改:`travelRequestDetailSetup.js` 新增 `applicationDetailEditor` 状态、`canEditApplicationDetailItem`、`openApplicationDetailEditor`、`saveApplicationDetailEdit` 等内联编辑流程;保存时复用 `runAiApplicationPreviewAction` 的 `save_draft` 路径和 `application_edit_claim_id`,保持更新原申请单。
|
||||
- 修改:`travel-request-detail-view.css` 收窄申请详情标签选择器,避免内联编辑内部 `span` 被误当字段名,并补充铅笔、确认、取消、编辑控件样式。
|
||||
- 验证:先运行新增目标断言失败,确认旧按钮仍存在;实现后 `node --test --test-name-pattern "draft or returned application detail edits allowed facts inline" web/tests/travel-request-detail-risk-advice.test.mjs` 通过;`node --test web/tests/travel-request-detail-risk-advice.test.mjs` 62 条通过;`node web/tests/ai-application-preview-actions.test.mjs` 通过;`git diff --check` 通过;`npm --prefix web run build` 通过。本地 `http://[::1]:5173/app/documents` 可达但跳转登录页,因无登录态未继续真实详情点击。
|
||||
- 影响:草稿/退回申请不再需要先打开 AI 助手才能改,用户可以直接在详情表格中改事由、时间、地点、出行方式;其它字段仍不可编辑,由原单或规则测算带入。
|
||||
- 17:02:补充修复:详情页因残留 `handleModifyApplication` 返回项导致 setup 阶段崩溃。
|
||||
- Git 提交检查:fetch 仍失败:fatal: unable to access 'https://www.caoxiaozhu.com:13002/YG-Soft/X-Financial.git/': LibreSSL SSL_connect: SSL_ERROR_SYSCALL in connection to www.caoxiaozhu.com:13002;upstream `origin/main`;upstream 新提交:未发现;本地 ahead 提交仍为 8a2ae6eb、992cf71f、54356ba8、e9d7c56d。
|
||||
- 修改:`travelRequestDetailSetup.js` 删除 return 对象里已经不存在的 `handleModifyApplication`,避免详情页初始化时抛 `ReferenceError`。
|
||||
- 修改:`travel-request-detail-risk-advice.test.mjs` 为内联编辑回归用例增加 `handleModifyApplication` 不得残留的断言;先运行目标用例确认失败,再删除残留返回项后确认转绿。
|
||||
- 验证:`node --test --test-name-pattern "draft or returned application detail edits allowed facts inline" web/tests/travel-request-detail-risk-advice.test.mjs` 先失败后通过;`node --test web/tests/travel-request-detail-risk-advice.test.mjs` 62 条通过;`node web/tests/ai-application-preview-actions.test.mjs` 通过;`git diff --check` 通过;`npm --prefix web run build` 通过。真实本地 `[::1]:5173` 以管理员打开 `AG2YUJ9FB` 详情页无控制台错误;切换申请人 `caoxiaozhu@xf.com` 后同页可见 `申请详情` 和单号,铅笔按钮 5 个,点击后出现 1 个编辑控件,取消后无保存副作用。
|
||||
- 影响:申请/报销详情页 setup 不再因为旧按钮处理函数缺失而白屏,内联铅笔编辑入口保留。
|
||||
@@ -0,0 +1,116 @@
|
||||
# 写日志技能拆分 概念文档
|
||||
|
||||
更新时间:2026-06-25
|
||||
|
||||
文档路径:document/development/2026-06-25/feature/agent-change-log-split/CONCEPT.md
|
||||
|
||||
## 功能一句话
|
||||
|
||||
把原来单文件三段式工作日志拆成按日期聚合的功能点文档、bug 修复日志和每日 17:00 综合工作日志。
|
||||
|
||||
## 背景与问题
|
||||
|
||||
- 当前现状:旧 `agent-change-log` 把所有变更追加到 `document/work-log/YYYY-MM-DD.md`,并固定包含 `当日工作内容`、`遗留问题`、`TODO`。
|
||||
- 用户痛点:功能点规划、bug 修复和当天综合复盘混在一个日志文件里,后续追溯时难以按功能或问题拆开看。
|
||||
- 业务影响:开发资料会越来越长,bug 修复证据和功能点设计边界容易互相干扰。
|
||||
- 为什么现在需要做:`write-development-docs` 已经改为 `document/development/YYYY-MM-DD/feature/<功能点>/`,日志能力也需要跟随同一日期根目录拆分。
|
||||
|
||||
## 目标与非目标
|
||||
|
||||
### 目标
|
||||
|
||||
- [G1] bug 修复记录落到 `document/development/YYYY-MM-DD/dev-logs/bugs/<bug-slug>.md`。
|
||||
- [G2] bug 日志只保留修复记录,不再写 `遗留问题` 和 `TODO` 两块。
|
||||
- [G3] 每天 17:00 汇总当天 `feature/` 和 `dev-logs/bugs/`,生成 `work-logs.med`。
|
||||
- [G4] 保留 Git 双向检查,继续识别 upstream 新提交和本地 ahead 提交。
|
||||
|
||||
### 非目标
|
||||
|
||||
- [NG1] 本轮不迁移历史 `document/work-log/*.md`。
|
||||
- [NG2] 本轮不删除旧历史日志,避免破坏既有追溯。
|
||||
- [NG3] 本轮不把非 bug 提交强行写入 bug 日志。
|
||||
|
||||
## 用户与场景
|
||||
|
||||
- 目标用户:使用 Codex/Agent 维护 X-Financial 的开发者和后续接手的智能体。
|
||||
- 使用入口:`agent-change-log` Skill、`tools/agent-change-log/update_change_log.py`、post-commit hook、每日 17:00 Codex automation。
|
||||
- 核心场景:
|
||||
1. 修复 bug 后写入当天 `dev-logs/bugs/<bug-slug>.md`。
|
||||
2. 非 bug 功能点通过 `feature/<功能点>/CONCEPT.md` 和 `TODO.md` 沉淀。
|
||||
3. 每天 17:00 汇总功能点与 bug,生成 `work-logs.med`。
|
||||
- 异常场景:
|
||||
- 没有 feature 或 bug 时,综合日志明确写“未发现”。
|
||||
- 提交标题不像 bug 时,post-commit 自动日志跳过。
|
||||
|
||||
## 功能能力
|
||||
|
||||
- [C1] 输入能力:支持 `--kind bug`、`--kind auto`、`--kind summary` 三种模式。
|
||||
- [C2] 处理能力:按日期创建 `dev-logs/bugs`,按 bug slug 记录修复内容。
|
||||
- [C3] 输出能力:输出 bug 修复记录或每日 `work-logs.med`。
|
||||
- [C4] 状态与权限:沿用 Git fetch/status/log 检查,不主动 merge/rebase。
|
||||
- [C5] 边界与降级:官方 skill 校验脚本不可用时,用脚本单测、frontmatter 和 diff check 兜底。
|
||||
|
||||
## 方案设计
|
||||
|
||||
### 前端
|
||||
|
||||
当前不涉及。
|
||||
|
||||
### 后端
|
||||
|
||||
当前不涉及业务后端;只修改仓库级 Skill、脚本和 hook。
|
||||
|
||||
### 算法与规则
|
||||
|
||||
- 输入:commit subject、用户传入的 bug title/slug、当天 feature 和 bug 文档。
|
||||
- 流程:bug 模式写入 bug 文件;auto 模式识别 bug-like commit;summary 模式扫描 `feature/` 和 `dev-logs/bugs/` 后生成综合日志。
|
||||
- 输出:`dev-logs/bugs/*.md` 或 `work-logs.med`。
|
||||
- 解释:summary 中保留来源目录和综合分析,便于复盘追溯。
|
||||
|
||||
### 数据与契约
|
||||
|
||||
- 核心字段:日期、bug slug、bug title、Git 提交检查、修改、操作、验证、影响。
|
||||
- 状态枚举:`auto`、`bug`、`summary`。
|
||||
- 兼容策略:保留旧 `document/work-log` 历史,不新增旧格式。
|
||||
- 版本/审计:本轮变更通过本地 checkpoint commit 保留。
|
||||
|
||||
## 算法与公式
|
||||
|
||||
当前功能不涉及显式数学公式。
|
||||
|
||||
## 测试方案
|
||||
|
||||
后端:
|
||||
|
||||
- 当前不涉及后端服务。
|
||||
|
||||
前端:
|
||||
|
||||
- 当前不涉及前端构建。
|
||||
|
||||
集成:
|
||||
|
||||
- 运行 `python3 tools/agent-change-log/test_update_change_log.py`,覆盖 bug 日志、非 bug 自动跳过、每日综合日志聚合。
|
||||
|
||||
手工验证:
|
||||
|
||||
- 运行 `update_change_log.py --kind bug --dry-run` 和 `--kind summary --dry-run` 检查目标路径和输出内容。
|
||||
|
||||
## 指标与验收
|
||||
|
||||
- [A1] 功能验收:bug 日志路径为 `document/development/YYYY-MM-DD/dev-logs/bugs/<bug-slug>.md`。
|
||||
- [A2] 性能指标:脚本单测在 60s 内完成。
|
||||
- [A3] 质量指标:不再为新日志写入旧三段式 `document/work-log/YYYY-MM-DD.md`。
|
||||
- [A4] 安全/权限指标:脚本不做 merge/rebase,不删除历史日志。
|
||||
- [A5] 可观测性:17:00 automation 生成 `work-logs.med` 后报告路径和是否发现 feature/bug。
|
||||
|
||||
## 风险与开放问题
|
||||
|
||||
- 风险:`work-logs.med` 是按用户原文保留的扩展名,可能与常见 `.md` 扩展名不一致。
|
||||
- 已处理依赖:已创建 Codex automation 执行每日 17:00 summary。
|
||||
- 待确认:后续是否需要迁移历史 `document/work-log`。
|
||||
- 降级策略:automation 不可用时可手动运行 `python3 tools/agent-change-log/update_change_log.py --kind summary`。
|
||||
|
||||
## 本轮实现记录
|
||||
|
||||
- 2026-06-25:重写 `agent-change-log` Skill 和脚本,新增 split log 测试,创建每日 17:00 Codex automation。
|
||||
@@ -0,0 +1,61 @@
|
||||
# 写日志技能拆分 开发 TODO
|
||||
|
||||
更新时间:2026-06-25
|
||||
|
||||
文档路径:document/development/2026-06-25/feature/agent-change-log-split/TODO.md
|
||||
|
||||
## 使用规则
|
||||
|
||||
- 每个 TODO 必须对应 `CONCEPT.md` 中的目标、能力、方案或验收点。
|
||||
- 只有完成并验证后,才能把 `[ ]` 改成 `[x]`。
|
||||
- 勾选时在任务后补充简短证据,例如文件、接口、命令或验证结果。
|
||||
- 如果需求发生变化,先更新 `CONCEPT.md`,再调整本 TODO。
|
||||
|
||||
## 1. 调研与边界
|
||||
|
||||
- [x] [CONCEPT: 背景与问题] 确认旧日志 Skill、脚本、hook 和 AGENTS 仍指向 `document/work-log/YYYY-MM-DD.md`。
|
||||
证据:`rg -n "document/work-log|当日工作内容|遗留问题|TODO" AGENTS.md .codex/skills/agent-change-log tools/agent-change-log .githooks/post-commit`。
|
||||
- [x] [CONCEPT: 目标与非目标] 明确本轮不迁移历史 `document/work-log/*.md`。
|
||||
证据:`CONCEPT.md` 非目标已列明历史不迁移。
|
||||
|
||||
## 2. 契约与设计
|
||||
|
||||
- [x] [CONCEPT: 功能能力] 定义 `auto`、`bug`、`summary` 三种脚本模式。
|
||||
证据:`tools/agent-change-log/update_change_log.py` 的 `--kind` 参数。
|
||||
- [x] [CONCEPT: 数据与契约] 固定新路径 `document/development/YYYY-MM-DD/dev-logs/bugs` 与 `work-logs.med`。
|
||||
证据:`agent-change-log` Skill、AGENTS 和脚本常量。
|
||||
|
||||
## 3. 后端实现
|
||||
|
||||
- [x] [CONCEPT: 后端] 重写日志脚本,支持 bug 记录、auto 跳过非 bug、summary 聚合。
|
||||
证据:`tools/agent-change-log/update_change_log.py`。
|
||||
- [x] [CONCEPT: 后端] 更新 post-commit hook,改为 `--kind auto`。
|
||||
证据:`.githooks/post-commit`。
|
||||
|
||||
## 4. 算法/规则实现
|
||||
|
||||
- [x] [CONCEPT: 算法与规则] 实现 bug-like commit 识别规则。
|
||||
证据:`looks_like_bug()` 覆盖 `fix`、`bugfix`、`修复`、`失败`、`异常` 等关键词。
|
||||
- [x] [CONCEPT: 算法与规则] 实现 feature 和 bug 汇总生成 `work-logs.med`。
|
||||
证据:`build_daily_summary()`。
|
||||
|
||||
## 5. 前端实现
|
||||
|
||||
- [x] [CONCEPT: 前端] 当前不涉及前端页面。
|
||||
证据:本轮只修改仓库 Skill、脚本、hook、文档和 automation。
|
||||
|
||||
## 6. 测试与验证
|
||||
|
||||
- [x] [CONCEPT: 测试方案] 补充脚本回归测试。
|
||||
证据:`tools/agent-change-log/test_update_change_log.py`。
|
||||
- [x] [CONCEPT: 测试方案] 运行脚本单测。
|
||||
证据:`python3 tools/agent-change-log/test_update_change_log.py`,3 tests OK。
|
||||
- [x] [CONCEPT: 测试方案] dry-run 验证 bug 路径和 summary 路径。
|
||||
证据:`--kind bug --dry-run` 输出 `document/development/2026-06-25/dev-logs/bugs/draft-preview-disappears.md`;`--kind summary --dry-run` 输出 `document/development/2026-06-25/work-logs.med`。
|
||||
|
||||
## 7. 文档收尾
|
||||
|
||||
- [x] [CONCEPT: 指标与验收] 更新 `agent-change-log` Skill、AGENTS 和 README。
|
||||
证据:`.codex/skills/agent-change-log/SKILL.md`、`AGENTS.md`、`tools/agent-change-log/README.md`。
|
||||
- [x] [CONCEPT: 指标与验收] 创建每日 17:00 Codex automation。
|
||||
证据:automation id `x-financial-daily-split-work-log`。
|
||||
@@ -0,0 +1,31 @@
|
||||
# AI 对话 UI 样式重构与 SaaS 化设计方案
|
||||
|
||||
为了让 X-Financial AI 助手的对话界面展现更专业的金融与 SaaS 企业化视觉,我们对前端全局的 Markdown 渲染样式进行了重构。
|
||||
|
||||
## 设计目标
|
||||
- **中性色打底**:对话块背景、边框移除饱和偏色,统一采用 Slate/Neutral 冷灰色调(如 `#f8fafc` 和 `#cbd5e1`)。
|
||||
- **降低色彩冗余**:将非必须的高亮蓝色改为中性灰色,各单据卡片仅在状态字样与极淡头部透明底中体现状态点缀,避免彩色杂乱堆积。
|
||||
- **界面扁平微阴影**:移除带有斜向彩色发光的阴影与复杂的背景图案,采用 1px 实线描边配合微阴影,契合 Stripe, Jira 等现代 SaaS 产品规范。
|
||||
|
||||
## 详细参数定义
|
||||
- **引用块 (`blockquote` / `.ai-html-callout`)**:
|
||||
- 边框:`3px solid #cbd5e1`
|
||||
- 背景:`#f8fafc`
|
||||
- 文字:`#334155`
|
||||
- **信息网格 (`.ai-html-focus-grid`)**:
|
||||
- 边框:`3px solid #cbd5e1`
|
||||
- 标题颜色:`#475569` (Slate-600)
|
||||
- **单据卡片 (`.ai-document-card`)**:
|
||||
- 描边:`1px solid #e2e8f0`
|
||||
- 投影:`0 1px 2px 0 rgba(15, 23, 42, 0.05)`
|
||||
- 头部默认背景:`rgba(241, 245, 249, 0.5)`
|
||||
- 状态点缀色:
|
||||
- `is-success` (Teal-700): `#0f766e`
|
||||
- `is-warning` (Amber-700): `#b45309`
|
||||
- `is-danger` (Red-700): `#b91c1c`
|
||||
- is-pending (Blue-600): `#2563eb`
|
||||
|
||||
## 消息排版格式规范
|
||||
- **限制 Alert 引用块为单条**:在警示/阻塞状态下,只允许包含一条 `>` 引用块用作 Alert 提示。
|
||||
- **列表扁平化**:发起前核对结果等普通多维状态信息,一律不使用 `>` 引用块,合并为标准的无序列表展示,保持界面视觉扁平、整洁。
|
||||
- **常规表单数据平铺**:时间、单据编号等字段,均采用扁平加粗文本平铺,不再触发 focus-grid 等具有边框线的额外容器。
|
||||
@@ -0,0 +1,9 @@
|
||||
# 任务计划 (SaaS 风格视觉优化)
|
||||
|
||||
- [x] 优化全局 `blockquote` 与 `.ai-html-callout` 的色彩饱和度,改用 Slate 灰蓝色系打底。
|
||||
- [x] 优化 `.ai-html-focus-grid` 信息网格与竖线的亮蓝色调。
|
||||
- [x] 移除单据卡片上的 `ai-document-card-bg.png` 渐变背景图。
|
||||
- [x] 扁平化单据卡片的描边与中性阴影。
|
||||
- [x] 优化卡片语义状态角标与操作链接的 SaaS 蓝及点缀。
|
||||
- [x] 优化原生 Markdown 列表 `li::marker` 与表格外包边框的色彩层级。
|
||||
- [x] 重构预审与冲突消息的 Markdown 输出格式,消除不必要的 `>` 引用块,攻克“三条大竖杠”的排版痛点。
|
||||
@@ -0,0 +1,207 @@
|
||||
# 主题设置与企业沉稳 AI 模式 概念文档
|
||||
|
||||
更新时间:2026-06-25
|
||||
|
||||
文档路径:document/development/2026-06-25/feature/theme-settings-enterprise-ai-style/CONCEPT.md
|
||||
|
||||
## 功能一句话
|
||||
|
||||
将系统设置中的“界面皮肤”升级为“主题设置”,从单纯色板选择扩展为产品体验风格选择,并让 AI 模式在“企业沉稳”主题下呈现更符合企业级 SaaS 的低噪声、结构化、克制风格。
|
||||
|
||||
## 背景与问题
|
||||
|
||||
当前系统设置里的外观入口仍偏向“界面皮肤”语义,主要表达颜色和视觉皮肤选择。这个命名过窄,无法承载用户希望配置的完整体验风格。
|
||||
|
||||
现有 AI 模式默认更接近动感活泼风格,使用较多渐变、明亮色块和活跃视觉标识。它适合演示和助手化体验,但在企业级财务、审批、风控、报销场景中,容易显得色彩过重,不够沉稳。
|
||||
|
||||
用户希望在系统设置中明确提供主题类型,至少覆盖:
|
||||
|
||||
1. 动感活泼:保留当前这种更有活力、更有 AI 助手感的主题。
|
||||
2. 企业沉稳:符合企业 SaaS 风格,尤其 AI 模式下的对话图标、样式和整体风格需要克制、稳定,减少颜色渲染。
|
||||
3. 专业智能:作为第三类默认方案,介于前两者之间,保留少量智能化识别感,但整体更收敛。
|
||||
|
||||
## 目标与非目标
|
||||
|
||||
目标:
|
||||
|
||||
- 将“界面皮肤”入口重命名为“主题设置”,让用户理解这里配置的是整体体验风格。
|
||||
- 将主题从一组颜色选项收敛成三类可理解的主题类型。
|
||||
- 在“企业沉稳”主题下,让 AI 模式呈现企业后台应用的专业感。
|
||||
- 保持现有设置保存链路稳定,优先复用当前 appearance 配置和主题变量机制。
|
||||
- 为后续进一步扩展租户品牌色、暗色模式、组件密度预留结构边界。
|
||||
|
||||
非目标:
|
||||
|
||||
- 不重做整个系统设置信息架构。
|
||||
- 不一次性重写所有业务页面的视觉风格。
|
||||
- 不改变 AI 意图识别、报销流程、审批逻辑和后端业务规则。
|
||||
- 不新增复杂的租户级主题发布、审批或版本管理能力。
|
||||
- 不引入新的前端主题框架或额外依赖。
|
||||
|
||||
## 用户与场景
|
||||
|
||||
管理员在系统设置中进入“主题设置”,选择适合当前组织的产品体验风格。
|
||||
|
||||
演示、培训、轻量工作台场景可以使用“动感活泼”,保留当前更有活力的 AI 交互表达。
|
||||
|
||||
企业正式生产环境可以使用“企业沉稳”,让财务、审批、风控和 AI 对话看起来像成熟的企业应用,而不是营销页或玩具化助手。
|
||||
|
||||
希望保留智能化识别但又不希望过度活泼的组织,可以使用“专业智能”。
|
||||
|
||||
## 功能能力
|
||||
|
||||
主题设置页面需要提供三类主题:
|
||||
|
||||
- 动感活泼:当前风格延续,允许渐变、轻动效和更明显的 AI 识别色。
|
||||
- 企业沉稳:低饱和色、白灰底、轻描边、少阴影、少渐变,强调信息层级和业务可信度。
|
||||
- 专业智能:更克制的智能风格,允许小面积蓝灰、紫灰或品牌色点缀,但避免大面积彩色渲染。
|
||||
|
||||
页面文案调整:
|
||||
|
||||
- 左侧菜单从“界面皮肤”调整为“主题设置”。
|
||||
- 页面标题从“界面皮肤与企业主色”调整为“主题风格与界面体验”。
|
||||
- 保存反馈从“界面皮肤已保存”调整为“主题设置已保存”。
|
||||
- 说明文案从“皮肤/配色”改为“主题/体验风格”。
|
||||
|
||||
AI 模式联动:
|
||||
|
||||
- 动感活泼:保留当前 AI 模式视觉语言。
|
||||
- 企业沉稳:对 AI 对话区、消息气泡、工具调用状态、思考过程、图标、卡片、提示块做克制化覆写。
|
||||
- 专业智能:保留轻量 AI 识别感,但降低渐变、发光、背景装饰和高饱和强调色。
|
||||
|
||||
## 方案设计
|
||||
|
||||
配置模型优先沿用当前系统设置外观配置,避免为了命名调整引入后端迁移风险。
|
||||
|
||||
首期可以继续复用 `appearanceForm.themeSkin` 作为持久化字段,将值从原先色板语义逐步映射为主题语义:
|
||||
|
||||
- `vivid`:动感活泼。
|
||||
- `enterprise`:企业沉稳。
|
||||
- `intelligent`:专业智能。
|
||||
|
||||
为了减少现有 CSS 变量和本地存储影响,前端可以在兼容期保留 `themeSkin` 字段,同时在 DOM 上补充更清晰的主题标识:
|
||||
|
||||
```text
|
||||
document.documentElement.dataset.themeSkin = value
|
||||
document.documentElement.dataset.themeMode = value
|
||||
```
|
||||
|
||||
旧值兼容策略:
|
||||
|
||||
- 旧的 `sky`、`blue`、`emerald` 等明亮主题默认映射到“动感活泼”。
|
||||
- 旧的 `navy`、`slate` 等偏稳重主题默认映射到“企业沉稳”。
|
||||
- 无法识别的值回退到“企业沉稳”,保证生产环境默认更克制。
|
||||
|
||||
设置页结构:
|
||||
|
||||
- 保留现有表单保存机制。
|
||||
- 将原色板卡片改为三张主题选项卡。
|
||||
- 每个主题卡展示名称、适用场景、视觉关键词和小型预览。
|
||||
- 当前选中主题需要有明确选中态,但避免大面积彩色边框。
|
||||
|
||||
企业沉稳 AI 模式样式:
|
||||
|
||||
- 通过 `[data-theme-mode="enterprise"]` 或 `[data-theme-skin="enterprise"]` 覆写 `personal-workbench-ai-mode.css` 中的 AI 模式变量。
|
||||
- 背景从多层 radial-gradient 收敛为白灰底或极轻线性渐变。
|
||||
- 对话气泡使用白底、浅灰描边和稳定文字层级。
|
||||
- AI 图标使用低饱和单色或品牌主色的小面积点缀。
|
||||
- 思考过程、工具调用、风险提示等区域使用结构化信息块,减少发光、彩色渐变和装饰性元素。
|
||||
- 风险、成功、警告等语义色保留,但只在图标、状态条或小面积标签上表达。
|
||||
|
||||
专业智能主题样式:
|
||||
|
||||
- 保留少量 AI 识别色,例如主色强调、轻量渐变按钮或小面积状态标识。
|
||||
- 背景和卡片仍以企业应用的可读性为主。
|
||||
- 避免全屏强背景、过多彩色阴影和过密装饰。
|
||||
|
||||
## 算法与公式
|
||||
|
||||
本功能不涉及复杂算法。
|
||||
|
||||
需要定义稳定的主题值映射函数:
|
||||
|
||||
```text
|
||||
normalizeThemeMode(rawTheme):
|
||||
if rawTheme in ["vivid", "sky", "blue", "emerald", "purple"]:
|
||||
return "vivid"
|
||||
if rawTheme in ["enterprise", "navy", "slate", "gray"]:
|
||||
return "enterprise"
|
||||
if rawTheme in ["intelligent"]:
|
||||
return "intelligent"
|
||||
return "enterprise"
|
||||
```
|
||||
|
||||
主题应用顺序:
|
||||
|
||||
```text
|
||||
后端保存值 / 本地缓存值
|
||||
-> normalizeThemeMode
|
||||
-> 写入 appearanceForm.themeSkin
|
||||
-> 写入 root dataset
|
||||
-> 应用 CSS 变量
|
||||
-> AI 模式按 data-theme-mode 覆写组件样式
|
||||
```
|
||||
|
||||
## 测试方案
|
||||
|
||||
前端单元和静态测试:
|
||||
|
||||
- 断言系统设置外观入口显示为“主题设置”。
|
||||
- 断言页面标题显示为“主题风格与界面体验”。
|
||||
- 断言三类主题均可见:动感活泼、企业沉稳、专业智能。
|
||||
- 断言企业沉稳主题保存后写入稳定主题值。
|
||||
- 断言旧主题值能通过 normalize 逻辑回退到可识别主题。
|
||||
- 断言 AI 模式存在企业沉稳主题 CSS 钩子。
|
||||
|
||||
构建验证:
|
||||
|
||||
- 运行前端构建,确保主题设置改动不破坏现有页面。
|
||||
- 运行已有设置相关测试,确保系统设置保存链路不回归。
|
||||
|
||||
真实页面验收:
|
||||
|
||||
- 打开 `/app/settings?section=appearance`,确认左侧菜单、页面标题、三类主题和保存反馈符合预期。
|
||||
- 切换到“企业沉稳”后打开 AI 工作台,确认对话区域、图标、消息、思考过程和提示块明显减少彩色渲染。
|
||||
- 切回“动感活泼”,确认现有风格仍可正常展示。
|
||||
- 切到“专业智能”,确认介于两者之间,不退化为纯色板换色。
|
||||
|
||||
## 指标与验收
|
||||
|
||||
功能验收:
|
||||
|
||||
- “界面皮肤”在设置入口和页面主标题中完成改名。
|
||||
- 用户可以选择三类主题,而不是面对一堆颜色皮肤。
|
||||
- 选择主题后刷新页面仍保持选中态。
|
||||
- 企业沉稳主题下,AI 模式整体视觉明显更接近企业 SaaS。
|
||||
- 动感活泼主题不丢失现有活力风格。
|
||||
- 专业智能主题具备独立视觉边界。
|
||||
|
||||
设计验收:
|
||||
|
||||
- 企业沉稳主题下不出现大面积高饱和渐变背景。
|
||||
- AI 对话图标和卡片不依赖强发光、强阴影或多彩背景表达层级。
|
||||
- 风险、审批、单据、工具调用等业务信息优先使用结构化排版。
|
||||
- 主题卡片文字不溢出,不在移动端产生拥挤或重叠。
|
||||
|
||||
工程验收:
|
||||
|
||||
- 不新增前端依赖。
|
||||
- 不引入后端表结构迁移。
|
||||
- 旧主题值有明确兼容策略。
|
||||
- 相关测试、构建和真实页面验收通过。
|
||||
|
||||
## 风险与开放问题
|
||||
|
||||
第三类主题默认命名为“专业智能”。如果后续用户指定更贴合业务的名称,可以只替换展示文案,不影响主题值和实现结构。
|
||||
|
||||
仅靠全局 CSS 变量可能无法完全消除 AI 模式的活泼感。企业沉稳主题需要对 AI 模式局部样式做有针对性的覆写。
|
||||
|
||||
旧色板值如果直接隐藏,可能让已有用户困惑。首期需要在兼容层处理旧值,并保证保存一次后落到新的三类主题值。
|
||||
|
||||
如果后续需要租户品牌色和主题组合,应该把“主题模式”和“品牌主色”拆成两个独立配置,避免再次把体验风格退化成颜色选择。
|
||||
|
||||
## 本轮文档记录
|
||||
|
||||
本轮已完成主题设置功能的前端实现:系统设置入口改名为“主题设置”,主题选项收敛为“动感活泼 / 企业沉稳 / 专业智能”三类,并通过主题归一化兼容旧色板值。
|
||||
|
||||
企业沉稳主题已联动 AI 工作台样式:根节点写入 `data-theme-mode="enterprise"`,AI 模式背景、图标容器、输入框、消息、思考过程和建议动作改为低饱和、轻描边、少渲染的企业 SaaS 风格。
|
||||
@@ -0,0 +1,69 @@
|
||||
# 主题设置与企业沉稳 AI 模式 开发 TODO
|
||||
|
||||
更新时间:2026-06-25
|
||||
|
||||
文档路径:document/development/2026-06-25/feature/theme-settings-enterprise-ai-style/TODO.md
|
||||
|
||||
## 使用规则
|
||||
|
||||
- 每个任务都需要关联 `CONCEPT.md` 中的章节,格式为 `[CONCEPT: 章节名]`。
|
||||
- 完成实现后再勾选对应任务,不用文档勾选代替代码验证。
|
||||
- 涉及真实页面效果的任务,需要在 5173 页面完成验收后再标记完成。
|
||||
|
||||
## 1. 调研与边界
|
||||
|
||||
- [x] [CONCEPT: 背景与问题] 确认当前设置外观入口仍使用“界面皮肤”语义,后续需要改为“主题设置”。
|
||||
- [x] [CONCEPT: 方案设计] 确认当前主题能力主要依赖 `appearanceForm.themeSkin`、主题选项和根节点 dataset。
|
||||
- [x] [CONCEPT: 方案设计] 确认企业沉稳 AI 模式主要需要覆写 `personal-workbench-ai-mode.css` 中的背景、对话、图标和提示块样式。
|
||||
- [x] [CONCEPT: 风险与开放问题] 梳理旧色板值到三类主题值的完整映射清单。
|
||||
- [x] [CONCEPT: 用户与场景] 确认三类主题在设置页中的展示顺序和说明文案。
|
||||
|
||||
## 2. 契约与设计
|
||||
|
||||
- [x] [CONCEPT: 功能能力] 将主题枚举收敛为 `vivid`、`enterprise`、`intelligent`。
|
||||
- [x] [CONCEPT: 功能能力] 明确三类主题的中文名称、适用场景和视觉关键词。
|
||||
- [x] [CONCEPT: 方案设计] 设计 `normalizeThemeMode` 兼容函数,保证旧值和未知值都有稳定回退。
|
||||
- [x] [CONCEPT: 方案设计] 决定是否新增 `themeMode` 前端概念,并保持与 `themeSkin` 字段兼容。
|
||||
- [x] [CONCEPT: 指标与验收] 定义企业沉稳 AI 模式的视觉验收标准。
|
||||
|
||||
## 3. 后端实现
|
||||
|
||||
- [x] [CONCEPT: 方案设计] 评估后端 settings schema 是否需要补充主题枚举校验。
|
||||
- [x] [CONCEPT: 方案设计] 若继续复用 `themeSkin`,确保后端允许新主题值保存。
|
||||
- [ ] [CONCEPT: 测试方案] 补充或更新设置持久化测试,覆盖三类主题值。
|
||||
- [x] [CONCEPT: 风险与开放问题] 确认不需要数据库结构迁移,并在实现说明中记录。
|
||||
|
||||
## 4. 算法/规则实现
|
||||
|
||||
- [x] [CONCEPT: 算法与公式] 实现旧主题值到新主题值的 normalize 逻辑。
|
||||
- [x] [CONCEPT: 算法与公式] 为未知值设置默认回退策略,优先回退到“企业沉稳”。
|
||||
- [x] [CONCEPT: 算法与公式] 确保本地缓存、后端返回值和根节点 dataset 使用同一套归一化结果。
|
||||
|
||||
## 5. 前端实现
|
||||
|
||||
- [x] [CONCEPT: 功能能力] 将设置左侧菜单“界面皮肤”改为“主题设置”。
|
||||
- [x] [CONCEPT: 功能能力] 将页面标题改为“主题风格与界面体验”。
|
||||
- [x] [CONCEPT: 功能能力] 将保存反馈和说明文案从“皮肤”语义调整为“主题”语义。
|
||||
- [x] [CONCEPT: 功能能力] 将原色板式选项调整为三类主题卡片。
|
||||
- [x] [CONCEPT: 功能能力] 为“动感活泼”保留当前视觉风格。
|
||||
- [x] [CONCEPT: 方案设计] 为“企业沉稳”新增 AI 模式样式覆写。
|
||||
- [x] [CONCEPT: 方案设计] 为“专业智能”新增介于活泼和沉稳之间的样式边界。
|
||||
- [x] [CONCEPT: 指标与验收] 检查主题卡片、按钮和说明文字在移动端不溢出、不重叠。
|
||||
- [x] [CONCEPT: 方案设计] 保证刷新页面后主题选择和 AI 模式样式仍然一致。
|
||||
|
||||
## 6. 测试与验证
|
||||
|
||||
- [x] [CONCEPT: 测试方案] 更新设置页相关前端测试,断言“主题设置”和三类主题选项。
|
||||
- [x] [CONCEPT: 测试方案] 补充 normalize 逻辑测试,覆盖旧值、未知值和三类新值。
|
||||
- [x] [CONCEPT: 测试方案] 补充 AI 模式企业沉稳 CSS 钩子测试或静态断言。
|
||||
- [x] [CONCEPT: 测试方案] 运行前端设置相关定向测试。
|
||||
- [x] [CONCEPT: 测试方案] 运行 `npm --prefix web run build`。
|
||||
- [x] [CONCEPT: 测试方案] 运行 `git diff --check`。
|
||||
- [x] [CONCEPT: 测试方案] 在真实 5173 页面验收 `/app/settings?section=appearance`。
|
||||
- [ ] [CONCEPT: 测试方案] 在真实 5173 页面验收 AI 工作台三类主题切换效果。
|
||||
|
||||
## 7. 文档收尾
|
||||
|
||||
- [x] [CONCEPT: 本轮文档记录] 实现完成后更新本文勾选状态。
|
||||
- [x] [CONCEPT: 指标与验收] 在最终交付说明中记录测试、构建和真实页面验收结果。
|
||||
- [ ] [CONCEPT: 风险与开放问题] 若第三类主题命名发生变化,同步更新概念文档和测试描述。
|
||||
@@ -0,0 +1,37 @@
|
||||
# 多 task 串行推进时 task2(业务招待费报销)无法启动
|
||||
|
||||
## 修复记录
|
||||
|
||||
- 12:14:记录 bug 修复:多 task 串行推进时,task1(出差申请)做完后点击"继续处理费用报销",task2(业务招待费报销)根本无法启动,报销草稿交互不起来,task2 语义(招待费/2000元/昨天)全部丢失。
|
||||
- Git 提交检查:`git fetch --all --prune` 因远端 SSL 连接失败未拉到新内容;`HEAD..@{u}` 为空(无 upstream 新提交);`@{u}..HEAD` 本地领先 8 个提交,其中与本 bug 相关的前置提交为 `3a5664c4 feat(web): 多 task 串行推进`、`3e4b1e15 fix(web): 保存草稿/提交成功后也推进到下一个 task`、`43c3ff86 fix(web): steward plan 确认按钮直接拉起申请预览,不丢失 remaining tasks`。
|
||||
- 修改:
|
||||
- `web/src/composables/workbenchAiMode/useWorkbenchAiActionRouter.js`:`travel_reimbursement` 分支不再硬编码 `requiresApplicationBeforeReimbursement=true`,改为从 `steward_current_task.ontology_fields` 解析费用类型并调 `requiresApplicationBeforeReimbursement(expenseType)` 判断;用 `buildAiExpenseDraftPrefillValues` 把 task 语义(金额/时间/事由/地点)预填进报销草稿;透传 `steward_remaining_tasks`。`ai_application_start_inline` 分支也透传 `prefill_values` 和 `steward_remaining_tasks`,让"查不到申请单→发起申请单"后能回到 task2。`buildNextTaskSuggestedAction` payload 补 `steward_remaining_tasks: remainingTasks.slice(1)`,防 3+ task 断链。
|
||||
- `web/src/composables/workbenchAiMode/useWorkbenchAiExpenseFlow.js`:`startAiExpenseDraft` 扩展第 4 参 `options`(`prefillValues`/`stewardRemainingTasks`);`resolveAiExpenseApplicationLink` 接收 options,查不到申请单时按费用类型动态生成"确认发起业务招待申请"按钮(不再写死"出差申请"),并透传 `prefill_values`/`steward_remaining_tasks`;新增 `attachStewardRemainingTasks`/`resolveStewardRemainingTasks`/`resolveRequiredApplicationLabel`/`buildExpenseDraftNextTaskAction` helper,把 remaining tasks 上下文挂在 draft 上贯穿报销→关联申请单→pollLinkedDraftJob 全流程;`advanceAiExpenseDraft`、`linkAiExpenseApplication`、`replaceInlineAssistantMessage`、`resumePendingLinkedReimbursementDraftJobs` 均补 remaining tasks 透传。
|
||||
- `web/src/composables/workbenchAiMode/useWorkbenchAiApplicationPreviewFlow.js`:`buildApplicationPreviewNextTaskAction` payload 补 `steward_remaining_tasks: remainingTasks.slice(1)`。
|
||||
- `web/src/utils/aiExpenseDraftModel.js`:`createAiExpenseDraft` 新增第三参 `prefillValues`,按已填值推进 `stepKey` 到第一个未填字段;新增 `buildAiExpenseDraftPrefillValues` 把 task ontology 映射到草稿字段。
|
||||
- `web/src/composables/workbenchAiMode/workbenchAiMessageModel.js`:`createInlineMessage`/`normalizeRuntimeMessage`/`serializeRuntimeMessage` 三处补 `stewardRemainingTasks` 字段读写,避免消息重建/刷新丢失推进上下文。
|
||||
- `web/src/utils/aiWorkbenchConversationStore.js`:`normalizeMessage` 持久化时保留 `stewardRemainingTasks`。
|
||||
- 操作:未提交(工作区有大量预先存在的未提交改动,未自动提交)。容器 `local-x-financial-linux` 此前未运行,已 `docker start` 恢复。
|
||||
- 验证:在容器内(node v22.22.3)跑 `node --test tests/ai-expense-draft-model.test.mjs tests/ai-workbench-conversation-store.test.mjs tests/workbench-ai-action-router.test.mjs`,16 个测试全部通过(含新增的 `buildAiExpenseDraftPrefillValues`、`createAiExpenseDraft` 预填、`stewardRemainingTasks` 持久化、`travel_reimbursement` 分支预填+透传 4 个用例)。其余前端测试失败为基线已存在(git stash 对比确认,与本次改动无关,多为正则匹配源码文本的断言因工作区其他未提交改动而失败)。
|
||||
- 影响:用户一次提问含"出差申请 + 招待费报销"等多 task 时,task1 完成后 task2 现在能正常启动:招待费类型、2000元金额、时间、事由会预填到报销草稿;招待费需要前置招待申请单(业务规则保留),查不到时按钮文案按类型动态展示并承接语义,发起申请单后能回到 task2;3+ task 不再断链;刷新会话后推进上下文不丢失。
|
||||
|
||||
- 12:23:继续修复同一 bug:模型计划已经返回“出差申请 + 业务招待费报销”两个 task 时,AI 工作台入口只消费第一个申请 task,第二个 task 没有自动开始。
|
||||
- Git 提交检查:`git fetch --all --prune` 仍因 `LibreSSL SSL_connect: SSL_ERROR_SYSCALL` 失败;`HEAD..@{u}` 为空,未发现可见 upstream 新提交;`@{u}..HEAD` 本地领先 8 个提交:`43c3ff86 fix(web): steward plan 确认按钮直接拉起申请预览,不丢失 remaining tasks`、`3e4b1e15 fix(web): 保存草稿/提交成功后也推进到下一个 task`、`3a5664c4 feat(web): 多 task 串行推进 - task1 完成后自动展示 task2 确认按钮`、`d139a63e refactor(server): 意图识别改 LLM 驱动,规则只做闲聊拦截+resume 兜底`、`8a2ae6eb fix(server): gate_classify 复用 _classify_irrelevant_input 修复 off_topic 误杀`、`992cf71f refactor(server): Phase 1 图拓扑重构 - LangGraph 成为唯一编排者`、`54356ba8 refactor(server): scene 注册表骨架 + 统一门控管道设计文档`、`e9d7c56d feat(server): 会话上下文保留(LLM 历史 + 确定性兜底双保险)`。
|
||||
- 修改:`workbenchAiIntentPlannerModel.js` 在归一化模型计划时保留 application task 后面的 `stewardRemainingTasks`,并只在存在剩余 task 时放进可执行申请请求;`usePersonalWorkbenchAiMode.js` 把 remaining tasks 传入申请预览,并在预览生成后通过现有 `steward_continue_next_task` 路由自动启动下一个 task;低置信确认按钮 payload 也继续携带队列;`useWorkbenchAiApplicationPreviewFlow.js` 在普通申请预览完成后调用 `onPreviewReadyForNextTask`;`useWorkbenchAiActionRouter.js` 的 `ai_application_confirm_intent` 分支同样透传 remaining tasks 并注册自动续跑回调。
|
||||
- 操作:新增回归测试覆盖“模型返回申请 task + 业务招待报销 task 时,前端可执行请求保留第二个 task”,以及“低置信确认按钮不丢队列并提供自动续跑回调”;保留既有未提交工作区改动,没有回滚或提交其他文件。
|
||||
- 验证:先看到新增测试红灯(`plan.stewardRemainingTasks` 为 `undefined`,确认按钮 options 也没有 remaining tasks),修复后 `node --test web/tests/workbench-ai-intent-planner-model.test.mjs web/tests/workbench-ai-action-router.test.mjs` 通过 24/24;`git diff --check` 通过;`docker exec -w /app -e SERVER_VENV_DIR=/tmp/x-financial-server-venv x-financial-local-linux timeout 60s /tmp/x-financial-server-venv/bin/pytest -q server/tests/test_steward_planner.py -k 'future_travel_without_apply_word_as_application or uses_llm_for_multi_financial_demands'` 通过 2/2;同命令在 `local-x-financial-linux` 收集阶段因该容器缺 `langgraph` 失败,已改用当前映射 5173 的 `x-financial-local-linux` 验证;`npm --prefix web run build` 通过;真实 `http://localhost:5173/api/v1/steward/plans` 采样确认该用户句子返回 `expense_application` 与 `reimbursement` 两个 task。
|
||||
- 影响:用户输入“2月20-23日去上海出差3天,服务国网服务器部署,并且报销昨天的业务招待费2000元”时,前端不再在第一个申请预览处截断队列;申请预览生成后会自动把第二个业务招待费报销 task 交给现有报销流程继续处理,同时刷新/低置信确认路径也不丢 task 队列。
|
||||
|
||||
- 22:30:继续修复同一 bug:用户点击“保存草稿”后,task1 已经完成,但界面仍只展示“继续处理费用报销”按钮,没有自动开始 task2。
|
||||
- Git 提交检查:`git fetch --all --prune` 仍因 `LibreSSL SSL_connect: SSL_ERROR_SYSCALL` 失败;`HEAD..@{u}` 为空,未发现可见 upstream 新提交;`@{u}..HEAD` 本地仍领先 8 个提交:`43c3ff86`、`3e4b1e15`、`3a5664c4`、`d139a63e`、`8a2ae6eb`、`992cf71f`、`54356ba8`、`e9d7c56d`。
|
||||
- 修改:`useWorkbenchAiApplicationPreviewFlow.js` 在保存草稿/提交申请成功后,如果当前申请消息还有 `stewardRemainingTasks`,就调用 `onApplicationActionCompleted` 自动续跑下一项;自动续跑时结果消息只保留查看详情动作,不再额外保留“继续处理”按钮,避免重复触发同一个 task。`usePersonalWorkbenchAiMode.js` 将该回调接到 `startModelPlannedNextTask`;`useWorkbenchAiActionRouter.js` 的低置信确认路径也同步传入保存/提交成功回调。
|
||||
- 操作:先补红灯断言,要求工作台入口向申请预览流传 `onApplicationActionCompleted`,且申请预览流在保存/提交成功分支调用该回调;然后按同一条 `steward_continue_next_task` 路由复用原有报销启动逻辑。
|
||||
- 验证:红灯阶段 `node --test web/tests/workbench-ai-intent-planner-model.test.mjs` 失败在缺少 `onApplicationActionCompleted`;修复后 `node --test web/tests/workbench-ai-intent-planner-model.test.mjs` 17/17 通过,`node --test web/tests/workbench-ai-action-router.test.mjs` 7/7 通过;`git diff --check` 通过;`npm --prefix web run build` 通过。
|
||||
- 影响:复合任务里用户保存第一张出差申请草稿后,系统会立即进入第二个业务招待费报销任务,不再要求用户手动点击“继续处理费用报销”;低置信确认后再保存草稿也保持同样行为。
|
||||
|
||||
- 22:45:继续修复同一 bug:真实页面已经显示“申请草稿已保存”,但保存动作结束后没有继续拉起第二个业务招待费报销 task。
|
||||
- Git 提交检查:`git fetch --all --prune` 仍因 `LibreSSL SSL_connect: SSL_ERROR_SYSCALL` 失败;`HEAD..@{u}` 为空,未发现可见 upstream 新提交;`@{u}..HEAD` 本地领先 13 个提交:`6bdaeed6 chore: 忽略 .zcode 本地目录并更新规则表与开发日志`、`d5a8f847 refactor(web): 应用外壳/差旅详情/报销创建视图适配主题与多 task`、`c4b5fcc0 feat(web): AI 工作台多 task 串行推进与会话适配`、`5753899e feat(web): 主题皮肤系统与 LLM 设置面板重构`、`9c3fa80d feat(server): 设置持久化新增 LLM 模型表与主题字段`,以及前面记录过的 8 个本地 ahead 提交。
|
||||
- 修改:`useWorkbenchAiApplicationPreviewFlow.js` 在保存草稿/提交申请成功分支新增 `actionCompletedHandler`,优先使用本次 `executeInlineApplicationPreviewAction` / `startAiApplicationPreview` options 传入的 `onApplicationActionCompleted`,没有时再回落到 composable 初始化回调;自动保存草稿时同步把 `options.onApplicationActionCompleted` 透传给保存动作。`workbench-ai-application-context-submit.test.mjs` 新增“自动保存申请草稿后继续剩余 steward task”的行为测试;`workbench-ai-intent-planner-model.test.mjs` 更新结构断言,锁住 options 回调优先和自动保存透传契约。
|
||||
- 操作:先用新增测试复现红灯:自动保存时确实只触发初始化回调,`options` 里的续跑回调没有被使用;再做最小修复,让保存动作按本次调用上下文续跑下一任务。
|
||||
- 验证:红灯阶段 `node --test web/tests/workbench-ai-application-context-submit.test.mjs` 失败在 `fromOptions` 为 `undefined`;修复后该测试 2/2 通过;`node --test web/tests/workbench-ai-intent-planner-model.test.mjs` 17/17 通过;`node --test web/tests/workbench-ai-action-router.test.mjs` 7/7 通过;真实 `http://127.0.0.1:5173/api/v1/steward/plans` 采样确认该用户句子仍返回 `expense_application` + `reimbursement` 两个 task;`npm --prefix web run build` 通过;`git diff --check` 通过。
|
||||
- 影响:保存草稿结果消息到达后,前端会使用当前任务链路传下来的续跑回调立即处理剩余 task;用户截图里的“申请草稿已保存”不再是终点,后续业务招待费报销会自动进入现有报销流程。
|
||||
17
document/development/2026-06-26/work-logs.med
Normal file
17
document/development/2026-06-26/work-logs.med
Normal file
@@ -0,0 +1,17 @@
|
||||
# 2026-06-26 综合工作日志
|
||||
|
||||
生成时间:2026-06-26 17:45:36 CST
|
||||
来源:`feature/` 功能点文档与 `dev-logs/bugs/` bug 记录
|
||||
|
||||
## 今日功能点
|
||||
|
||||
- 今日未发现功能点文档。
|
||||
|
||||
## 今日 Bugs
|
||||
|
||||
- 多 task 串行推进时 task2(业务招待费报销)无法启动:影响:用户输入“2月20-23日去上海出差3天,服务国网服务器部署,并且报销昨天的业务招待费2000元”时,前端不再在第一个申请预览处截断队列;申请预览生成后会自动把第二个业务招待费报销 task 交给现有报销流程继续处理,同时刷新/低置信确认路径也不丢 task 队列。(文件:`multi-task-reimbursement-not-starting.md`)
|
||||
|
||||
## 综合分析
|
||||
|
||||
- 问题侧记录了 1 个 bug 修复。
|
||||
- 后续复盘优先看本文件,再回到对应功能点或 bug 文件追溯证据。
|
||||
17
document/development/2026-06-27/work-logs.med
Normal file
17
document/development/2026-06-27/work-logs.med
Normal file
@@ -0,0 +1,17 @@
|
||||
# 2026-06-27 综合工作日志
|
||||
|
||||
生成时间:2026-06-27 17:43:24 CST
|
||||
来源:`feature/` 功能点文档与 `dev-logs/bugs/` bug 记录
|
||||
|
||||
## 今日功能点
|
||||
|
||||
- 今日未发现功能点文档。
|
||||
|
||||
## 今日 Bugs
|
||||
|
||||
- 今日未发现 bug 修复记录。
|
||||
|
||||
## 综合分析
|
||||
|
||||
- 今日目录下暂无功能点或 bug 记录。
|
||||
- 后续复盘优先看本文件,再回到对应功能点或 bug 文件追溯证据。
|
||||
17
document/development/2026-06-28/work-logs.med
Normal file
17
document/development/2026-06-28/work-logs.med
Normal file
@@ -0,0 +1,17 @@
|
||||
# 2026-06-28 综合工作日志
|
||||
|
||||
生成时间:2026-06-28 18:30:35 CST
|
||||
来源:`feature/` 功能点文档与 `dev-logs/bugs/` bug 记录
|
||||
|
||||
## 今日功能点
|
||||
|
||||
- 今日未发现功能点文档。
|
||||
|
||||
## 今日 Bugs
|
||||
|
||||
- 今日未发现 bug 修复记录。
|
||||
|
||||
## 综合分析
|
||||
|
||||
- 今日目录下暂无功能点或 bug 记录。
|
||||
- 后续复盘优先看本文件,再回到对应功能点或 bug 文件追溯证据。
|
||||
@@ -0,0 +1,16 @@
|
||||
# 多 task 串行推进时 task2 无法启动(onPreviewReadyForNextTask 时序缺陷)
|
||||
|
||||
日期:2026-06-30
|
||||
文档路径:document/development/2026-06-30/dev-logs/bugs/multi-task-next-task-blocked-by-preview-ready-timing.md
|
||||
|
||||
## 修复记录
|
||||
- 11:18:记录 bug 修复:用户在 AI 工作台输入"出差申请 + 招待费报销"等多 task 时,task1(出差申请)保存草稿/提交成功后,task2(招待费报销)完全无法启动,界面停在"申请草稿已保存"。
|
||||
- Git 提交检查:`git fetch --all --prune` 成功;upstream `origin/main`;`HEAD..@{u}` 未发现 upstream 新提交;`@{u}..HEAD` 未发现本地 ahead 提交。工作区改动仅为本次 3 个源文件 + 2 个测试文件(另有一个预先存在的未提交改动 `server/rules/finance-rules/公司通信费报销规则.xlsx`,与本次无关)。
|
||||
- 根因:`onPreviewReadyForNextTask` 回调在 task1 申请核对表**刚生成、用户还没看、还没点保存草稿**时就立刻触发 `startModelPlannedNextTask`,提前把 task2 招待费报销拉起(`startAiExpenseDraft` 会 push 一条"选择费用报销"用户消息 + 报销 prompt)。两条流程的消息和状态互相打架,用户再在 task1 上点保存草稿时 `onApplicationActionCompleted` 又试图拉起 task2,但 task2 状态已被前面 `onPreviewReadyForNextTask` 搞乱,最终表现为"完全无反应"。运行时复现脚本时序铁证:预览生成后立即出现 `!!! onPreviewReadyForNextTask 触发(task1预览刚生成,用户还没操作)`,与串行推进的正确语义(task1 完成后才推进 task2)冲突。这是早期实现的残留——引入 `onApplicationActionCompleted`(task1 完成后触发)后,`onPreviewReadyForNextTask` 职责重叠且时序错误。
|
||||
- 修改(前端 web,3 个源文件):
|
||||
- `web/src/composables/workbenchAiMode/useWorkbenchAiApplicationPreviewFlow.js`:删除 `startAiApplicationPreview` 预览生成后的 `else if (onPreviewReadyForNextTask ...)` 提前推进分支(原 L622-L628),并加注释说明 task2 推进统一交给 `onApplicationActionCompleted` 在 task1 真正完成后触发。`executeInlineApplicationPreviewAction` 里 L466 的 `actionCompletedHandler` 回落逻辑保留不动(手动点保存草稿走 actionRouter 不传 options 回调,回落到模块级 `startModelPlannedNextTask`,这是正确的续跑路径)。
|
||||
- `web/src/composables/workbenchAiMode/usePersonalWorkbenchAiMode.js`:`startModelPlannedApplicationPreview` 调用 `startAiApplicationPreview` 时删除 `onPreviewReadyForNextTask: startModelPlannedNextTask` 一行,只保留 `onApplicationActionCompleted: startModelPlannedNextTask`(原 L769)。
|
||||
- `web/src/composables/workbenchAiMode/useWorkbenchAiActionRouter.js`:`ai_application_confirm_intent`(低置信确认按钮)分支删除 `onPreviewReadyForNextTask` 回调,只保留 `onApplicationActionCompleted`(原 L99-L104),消除低置信路径的同样时序缺陷。
|
||||
- 操作:先写复现脚本 `/tmp/repro-timing.mjs`(applicationFlow + 两个回调)锁定时序根因——修复前预览生成后立即触发推进回调,修复后无提前触发;再按计划小步改 3 个源文件 + 2 个测试文件;未提交(工作区有预先存在的无关 xlsx 改动,未自动提交)。
|
||||
- 验证:宿主机 node v22.22.3 跑 `node --test web/tests/workbench-ai-intent-planner-model.test.mjs web/tests/workbench-ai-action-router.test.mjs web/tests/workbench-ai-application-context-submit.test.mjs` 通过 27/27(含新增的时序回归用例 `workbench application preview does not continue next task until draft is saved or submitted`:断言预览生成时 `continuedTasks.length === 0`、保存草稿后才推进 task2 且走模块级续跑回调、自动续跑时不展示重复的"继续处理"按钮);`npm --prefix web run build` 通过(3.97s);复现脚本 `/tmp/repro-timing.mjs` 修复后事件序列只剩用户消息、无提前推进;真实 `http://localhost:5173/api/v1/steward/plans` 与 `/api/v1/steward/plans/stream` 采样确认该句子仍返回 `expense_application` + `reimbursement` 两个 task(后端拆分正确,本次未动后端)。
|
||||
- 影响:用户输入框提交"2月20-23日,去上海出差辅助国网仿生产服务器部署,并且报销昨天的上午招待费2000元"等多 task 时,task1 出差申请核对表生成后干净停下等用户操作,用户点保存草稿/直接提交成功后自动进入 task2 招待费报销(预填金额/时间/事由),不再出现两条流程打架导致 task2 完全无反应的问题。不影响后端、单 task 场景、autoSaveDraft 路径(它走 `executeInlineApplicationPreviewAction` 完成后触发 `onApplicationActionCompleted`,链路不变);低置信确认按钮路径也同步修复。
|
||||
243
document/development/AI意图规划器/UNIFIED_GATE_PIPELINE.md
Normal file
243
document/development/AI意图规划器/UNIFIED_GATE_PIPELINE.md
Normal file
@@ -0,0 +1,243 @@
|
||||
# 统一门控管道(Unified Gate Pipeline)
|
||||
|
||||
> 状态:**设计定稿,待实施**
|
||||
> 创建:2026-06-25
|
||||
> 关联:`CONCEPT.md`、`LANGGRAPH_RUNTIME_MIGRATION.md`
|
||||
|
||||
## 1. 为什么要做这件事
|
||||
|
||||
### 1.1 现状的致命问题
|
||||
|
||||
小财管家的门控(决定用户输入走哪条路)目前散落在 **7 个位置**,互相不知道对方的结论,每加一个场景要找 n 个地方改:
|
||||
|
||||
| # | 位置 | 文件 | 做的门控 |
|
||||
|---|---|---|---|
|
||||
| 1 | 前端 7 层 if/else | `usePersonalWorkbenchAiMode.js:858-913` `startInlineConversation` | 命令→文本动作→草稿→模型规划→报销→闲聊,每层各自 return |
|
||||
| 2 | 前端业务词预筛 | `workbenchAiIntentPlannerModel.js:shouldRequestWorkbenchAiIntentPlan` | 不含业务词的输入不发给后端 |
|
||||
| 3 | 后端 endpoint 补丁群 | `steward.py:create_steward_plan` | `_hydrate_required_application_gate` / `_inject_recent_conversation_history` / `_apply_context_resume` 三个补丁串在 `build_plan` 前后 |
|
||||
| 4 | 图条件边路由 | `steward_graph_planner.py:_route_after_prepare_context` | off_topic / model / fallback 三路 |
|
||||
| 5 | off_topic 关键词 | `steward_planner_fallback.py:_classify_irrelevant_input` + `STEWARD_BUSINESS_SIGNAL_KEYWORDS` | 写死的信号词元组 |
|
||||
| 6 | 候选流程歧义 | `steward_planner_extraction.py:_looks_like_ambiguous_travel_flow` | 独立的正则判定 |
|
||||
| 7 | 图后意图处理 | `usePersonalWorkbenchAiMode.js:813-835` `executeModelPlannedWorkbenchIntent` | 前端再判一遍 task_type 决定渲染申请预览还是报销 |
|
||||
|
||||
**根因:没有单一的决策点。** LangGraph 图只承担了"意图识别"这一个职责,控制流泄漏到了 endpoint 层和前端 composable,形成两个影子编排器。
|
||||
|
||||
### 1.2 不持久的判据
|
||||
|
||||
加一个场景(如"查报销进度")的成本是 **O(n)**——必须同步改前端门控、后端补丁、图条件边、off_topic 关键词、候选流程判定等多处,漏一处就静默出错。本次会话已经在不断验证这个痛点:每个新场景(查询、低置信度、上下文恢复)都是往不同位置打补丁。
|
||||
|
||||
## 2. 目标:接入成本 O(1)
|
||||
|
||||
加一个新场景,**全文改动只有一处**:
|
||||
|
||||
```python
|
||||
# server/src/app/services/scenes/scene_query_reimbursement_progress.py
|
||||
register_scene(SceneDescriptor(
|
||||
scene_id="query_reimbursement_progress",
|
||||
label="报销进度查询",
|
||||
signal_keywords=("报销进度", "报销状态", "审批进度", "审批到哪了"),
|
||||
ontology_fields=("claim_no", "time_range"),
|
||||
gate=GateRule.CHOICE, # 不走候选流程、不走 off_topic
|
||||
can_resume=False, # 不参与上下文恢复
|
||||
route=SceneRoute.HANDLER_ONLY, # 不走 LLM,直接执行 handler
|
||||
handler=execute_progress_query, # 纯函数:检索 + 拼装
|
||||
prompt_fragment="用户询问报销审批进度/状态时,识别为 query_reimbursement_progress。",
|
||||
))
|
||||
```
|
||||
|
||||
**不改图、不改 endpoint、不改前端门控、不改 extraction。** 判断规则、路由、执行、槽位、恢复能力在同一个 descriptor 里声明,不会割裂。
|
||||
|
||||
## 3. 目标架构
|
||||
|
||||
### 3.1 后端:图成为唯一编排者
|
||||
|
||||
```
|
||||
POST /api/v1/steward/plans
|
||||
↓
|
||||
endpoint: 纯 IO (收请求 → graph.invoke → 返响应,零编排)
|
||||
↓
|
||||
LangGraph StateGraph (唯一编排者):
|
||||
START
|
||||
→ load_context 读最近10条历史 + steward_state + hydrate
|
||||
→ gate_classify 统一门控:按 registry 规则裁决 scene + route
|
||||
→ route 分支
|
||||
├─ off_topic → off_topic_reply
|
||||
├─ handler_only → execute_scene_handler (查询/命令类,不走 LLM)
|
||||
├─ resume → resume_recent_task ("再提交"确定性恢复)
|
||||
├─ ambiguous_flow → pending_flow_confirmation
|
||||
└─ model_intent → detect_model_intent → {done | fallback}
|
||||
→ attach_action_steps
|
||||
→ persist_state 写 message + steward_state
|
||||
→ END
|
||||
```
|
||||
|
||||
**endpoint 层只剩 3 行**:`planner = build(db); plan = planner.build_plan(payload); return plan`。所有 hydrate/inject/resume 全部搬进图节点。
|
||||
|
||||
### 3.2 前端:退化为纯渲染
|
||||
|
||||
```
|
||||
用户输入
|
||||
↓
|
||||
前端: 不再自己决策,统一发给后端
|
||||
POST /steward/plans { message, conversation_id }
|
||||
↓
|
||||
后端返回 StewardPlanResponse:
|
||||
- plan.next_action 告诉前端该渲染什么
|
||||
- plan.tasks[].task_type 告诉前端该用哪个渲染器
|
||||
- plan.suggested_actions 告诉前端该显示哪些按钮
|
||||
↓
|
||||
前端: 按 response 的指令渲染(申请预览/报销预览/查询结果/纯文本回复)
|
||||
```
|
||||
|
||||
前端的 7 层 if/else **全部移除**,替换为:
|
||||
```js
|
||||
async function startInlineConversation(prompt) {
|
||||
const plan = await fetchStewardPlan({ message: prompt, conversation_id: conversationId.value })
|
||||
renderPlanResponse(plan) // 按 plan.next_action / task_type 分发到对应渲染器
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 SceneDescriptor:场景的唯一声明
|
||||
|
||||
```python
|
||||
@dataclass(frozen=True)
|
||||
class SceneDescriptor:
|
||||
scene_id: str # 唯一标识,等同 task_type
|
||||
label: str # 中文标签
|
||||
signal_keywords: tuple[str, ...] # 规则识别的关键词(聚合进 off_topic 信号池)
|
||||
ontology_fields: tuple[str, ...] # 该场景允许的槽位
|
||||
gate: GateRule # 门控规则(见 3.4)
|
||||
route: SceneRoute # 路由策略(见 3.5)
|
||||
handler: Callable | None # 执行函数(handler_only 路由用)
|
||||
can_resume: bool = False # 是否参与"再提交"上下文恢复
|
||||
action_steps_builder: Callable = ... # 动作步骤生成
|
||||
prompt_fragment: str = "" # 注入 LLM system prompt 的识别指引
|
||||
priority: int = 100 # gate_classify 的匹配优先级(小优先)
|
||||
flow_id: str | None = None # 候选流程用;查询/命令类为 None
|
||||
```
|
||||
|
||||
### 3.4 GateRule:门控规则枚举
|
||||
|
||||
```python
|
||||
class GateRule(Enum):
|
||||
OFF_TOPIC = "off_topic" # 非业务输入,走 off_topic_reply
|
||||
CHOICE = "choice" # 明确的业务选择,走 handler/model
|
||||
AMBIGUOUS_FLOW = "ambiguous_flow" # 话术歧义,走候选流程确认
|
||||
MODEL_ONLY = "model_only" # 只走 LLM function call,不参与规则匹配
|
||||
```
|
||||
|
||||
### 3.5 SceneRoute:路由策略枚举
|
||||
|
||||
```python
|
||||
class SceneRoute(Enum):
|
||||
HANDLER_ONLY = "handler_only" # 不走 LLM,直接执行 handler(查询/命令类)
|
||||
MODEL_INTENT = "model_intent" # 走 LLM function call(申请/报销类)
|
||||
OFF_TOPIC = "off_topic" # 走 off_topic 回复
|
||||
RESUME = "resume" # 走确定性上下文恢复
|
||||
AMBIGUOUS = "ambiguous" # 走候选流程确认
|
||||
```
|
||||
|
||||
## 4. gate_classify 节点的裁决逻辑(唯一决策点)
|
||||
|
||||
```python
|
||||
def gate_classify(state) -> dict:
|
||||
"""统一门控:按优先级遍历 registry,输出 scene_id + route。"""
|
||||
message = state["message"]
|
||||
steward_state = state["steward_state"]
|
||||
history = state["recent_history"]
|
||||
|
||||
# ① off_topic 门:聚合所有场景的 signal_keywords,无命中 → off_topic
|
||||
if not _matches_any_signal(message):
|
||||
return {"scene_id": "off_topic", "route": SceneRoute.OFF_TOPIC}
|
||||
|
||||
# ② resume 门:用户说"再提交"+ state 有可恢复 flow
|
||||
resume_scene = _check_resume(message, steward_state)
|
||||
if resume_scene:
|
||||
return {"scene_id": resume_scene, "route": SceneRoute.RESUME}
|
||||
|
||||
# ③ 规则匹配门:按 priority 遍历,命中 signal_keywords 的场景
|
||||
for scene in registry.scenes_sorted_by_priority():
|
||||
if scene.gate == GateRule.CHOICE and _matches_keywords(message, scene.signal_keywords):
|
||||
return {"scene_id": scene.scene_id, "route": scene.route}
|
||||
|
||||
# ④ LLM 门:规则未命中,走 model function call
|
||||
return {"scene_id": None, "route": SceneRoute.MODEL_INTENT}
|
||||
```
|
||||
|
||||
**所有门控收敛到这一个函数。** off_topic 信号词、resume 判断、规则匹配、LLM 兜底,全部在这里按固定顺序裁决。
|
||||
|
||||
## 5. 文件结构
|
||||
|
||||
```
|
||||
server/src/app/services/
|
||||
scenes/ # 场景声明(每个场景一个文件)
|
||||
__init__.py # 注册所有场景
|
||||
scene_registry.py # SceneRegistry 单例 + 查询方法
|
||||
scene_descriptor.py # SceneDescriptor dataclass
|
||||
scene_expense_application.py # 出差申请场景
|
||||
scene_reimbursement.py # 报销场景
|
||||
scene_query_travel_standard.py# 差旅标准查询场景
|
||||
gate_rules.py # GateRule / SceneRoute 枚举
|
||||
steward_graph_planner.py # 图:load_context/gate_classify/.../persist_state
|
||||
steward_scene_handlers.py # 各场景的 handler 纯函数
|
||||
```
|
||||
|
||||
## 6. 迁移路径(分阶段,每阶段可独立验证)
|
||||
|
||||
### Phase 1:建场景注册表 + 收口后端门控(后端自闭环)
|
||||
|
||||
**目标**:后端 endpoint 零编排,图成为唯一编排者。
|
||||
|
||||
1. 新建 `scenes/` 目录,实现 `SceneDescriptor` / `SceneRegistry` / `GateRule` / `SceneRoute`
|
||||
2. 把现有 3 个场景(expense_application / reimbursement / query_travel_standard)迁入 descriptor
|
||||
3. 新增图节点:`load_context`、`gate_classify`、`resume_recent_task`、`persist_state`
|
||||
4. 把 endpoint 的 4 个补丁函数搬进图节点
|
||||
5. endpoint 退化为 3 行
|
||||
|
||||
**验证**:后端全量测试绿 + 端到端(上海出差/再提交/查差旅标准)通过
|
||||
|
||||
### Phase 2:前端退化为纯渲染
|
||||
|
||||
**目标**:前端移除 7 层 if/else,统一发给后端。
|
||||
|
||||
1. `startInlineConversation` 改为:`fetchStewardPlan → renderPlanResponse`
|
||||
2. 按 `plan.next_action` / `task_type` 分发到渲染器(申请预览/报销预览/查询结果/纯文本)
|
||||
3. 移除 `shouldRequestWorkbenchAiIntentPlan`、`isReimbursementCreationIntent`、`isLowConfidenceTravelApplicationPlan` 等前端门控函数
|
||||
4. 保留并复用现有渲染组件(applicationPreview、stewardPlan 渲染逻辑不重写)
|
||||
|
||||
**验证**:前端测试绿 + 人工验证各场景渲染正确
|
||||
|
||||
### Phase 3:清理冗余
|
||||
|
||||
1. 删除 `steward_planner_fallback.py` 的 `_classify_irrelevant_input` 独立门控
|
||||
2. 删除 `_looks_like_ambiguous_travel_flow` 独立判定(收进 gate_classify)
|
||||
3. 统一 signal_keywords 来源(registry 唯一)
|
||||
4. 删除旧的 endpoint 补丁函数
|
||||
|
||||
## 7. 验证标准(持久性的可衡量判据)
|
||||
|
||||
接入一个新场景(如"查报销进度")时,**改动文件清单必须且仅限于**:
|
||||
|
||||
| 文件 | 改动 |
|
||||
|---|---|
|
||||
| `scenes/scene_query_reimbursement_progress.py` | 新建:1 个 SceneDescriptor + 1 个 handler 函数 |
|
||||
| `scenes/__init__.py` | 加 1 行 import + register |
|
||||
|
||||
**如果接入时需要动 `steward_graph_planner.py` / `steward.py` / 前端 composable / extraction.py / fallback.py 中任何一个,说明架构没有收口成功。** 这是验收的硬标准。
|
||||
|
||||
## 8. 不改变的东西
|
||||
|
||||
- `RuntimeChatService`(模型供应商抽象):不动
|
||||
- `StewardActionExecutor`(执行分发):已在 registry 驱动,不动
|
||||
- `AgentConversationService`(消息持久化):不动,只是调用点从 endpoint 搬进图节点
|
||||
- LangGraph 的 `StateGraph` / `interrupt` / checkpoint:继续用,只是节点职责更完整
|
||||
- 现有渲染组件(applicationPreview 表格、stewardPlan 消息):复用,不重写
|
||||
|
||||
## 9. 风险与对策
|
||||
|
||||
| 风险 | 对策 |
|
||||
|---|---|
|
||||
| 图重构引入回归 | Phase 1 每搬一个节点跑一次全量测试 |
|
||||
| 前端去掉门控后某些场景渲染不出 | Phase 2 先保留渲染器映射,只改"谁决策"不改"怎么渲染" |
|
||||
| gate_classify 性能(遍历 registry) | 场景数 <20,关键词正则匹配 O(1),无性能问题 |
|
||||
| LLM 历史注入搬进图后 token 超限 | 保持 limit=10 不变 |
|
||||
@@ -1,132 +0,0 @@
|
||||
# Agent链路追踪中心 概念文档
|
||||
|
||||
## 功能一句话
|
||||
|
||||
为 Orchestrator 全链路运行提供统一 trace 采集、查询和前端回放入口,让管理员能按 `run_id` 或 `conversation_id` 还原一次 Agent 对话从意图识别到最终回复的全过程。
|
||||
|
||||
## 背景与问题
|
||||
|
||||
- 当前现状:系统已有 `agent_runs`、`agent_tool_calls`、`semantic_parse_logs` 和对话消息,但它们分散在运行记录、工具调用、语义解析与系统日志中。
|
||||
- 用户痛点:线上 Agent 回答异常时,只能看局部日志或 Hermes 工作记录,难以判断问题出在意图路由、知识检索、规则引擎、数字员工任务还是回复生成。
|
||||
- 业务影响:Agent 链路越长,排障成本越高;没有可重放视图会影响交付、演示和运维可信度。
|
||||
|
||||
## 目标与非目标
|
||||
|
||||
### 目标
|
||||
|
||||
- [G1] 后端沉淀统一的 Agent trace 事件模型,按运行顺序记录关键阶段输入、输出、状态、耗时和错误。
|
||||
- [G2] 提供 trace 查询接口,支持按 `run_id` 查看单次运行,按 `conversation_id` 查看多轮会话链路。
|
||||
- [G3] 前端新增 Agent Trace Center 入口,展示运行时间线、工具调用、语义解析、路由上下文和最终回复。
|
||||
- [G4] 保留现有 Agent Run / ToolCall 数据结构,避免破坏数字员工工作记录和系统日志页面。
|
||||
|
||||
### 非目标
|
||||
|
||||
- [NG1] 本轮不做重新执行真实业务动作的“调试重跑”,只做历史重放。
|
||||
- [NG2] 本轮不接入 OpenTelemetry、Jaeger 等外部分布式追踪系统。
|
||||
- [NG3] 本轮不改造总账、预算、报销审批等业务语义。
|
||||
- [NG4] 本轮不做跨服务链路采样策略和海量归档策略。
|
||||
|
||||
## 用户与场景
|
||||
|
||||
- 目标用户:系统管理员、财务系统运维、Agent 能力开发者、实施顾问。
|
||||
- 使用入口:系统日志详情、数字员工工作记录、报销助手消息中的 `run_id`、新增 Trace Center 页面。
|
||||
- 核心场景:
|
||||
- 管理员打开某次异常回复,查看每一步输入输出和耗时。
|
||||
- 实施人员按会话查看多轮上下文,判断上下文是否被错误继承。
|
||||
- 开发者定位工具调用失败、语义识别降级或路由选错 Agent 的原因。
|
||||
- 异常场景:
|
||||
- 运行失败但没有工具调用时,仍展示已记录的 orchestration 阶段。
|
||||
- 旧数据没有 trace event 时,接口回退展示 `agent_runs`、`semantic_parse`、`tool_calls`。
|
||||
|
||||
## 功能能力
|
||||
|
||||
- [C1] 输入能力:接收 `run_id`、`conversation_id`、Agent、状态、来源、关键字等查询条件。
|
||||
- [C2] 采集能力:记录 `received`、`context_hydrated`、`semantic_parsed`、`agent_selected`、`capability_selected`、`tool_invoked`、`response_built`、`conversation_updated`、`failed` 等事件。
|
||||
- [C3] 输出能力:返回 trace 摘要、事件时间线、工具调用、语义解析、路由 JSON、最终回复和关联会话消息。
|
||||
- [C4] 状态与权限:复用现有登录与页面权限,管理员/可访问设置页用户可查看全量 trace。
|
||||
- [C5] 边界与降级:旧运行没有 trace events 时,按现有 run/tool/semantic 数据合成最小时间线。
|
||||
|
||||
## 方案设计
|
||||
|
||||
### 前端
|
||||
|
||||
- 页面/组件:
|
||||
- 新增 `AgentTraceCenterView` 或设置页内 Trace Center 分区。
|
||||
- 新增 trace 详情组件,复用现有日志详情的直角企业级视觉。
|
||||
- 从日志详情、数字员工工作记录、报销助手操作反馈中可跳转到 trace 详情。
|
||||
- 交互状态:
|
||||
- 列表支持关键字、状态、Agent、来源筛选。
|
||||
- 详情展示左侧时间线、右侧输入输出 JSON、顶部摘要。
|
||||
- 支持加载、空态、错误态和刷新。
|
||||
- 展示规则:
|
||||
- 事件按 `started_at`、`sequence` 升序展示。
|
||||
- 失败事件突出错误信息。
|
||||
- 大 JSON 使用可滚动代码块,避免撑破页面。
|
||||
|
||||
### 后端
|
||||
|
||||
- 接口/服务:
|
||||
- `GET /api/v1/agent-traces`:查询 trace 列表。
|
||||
- `GET /api/v1/agent-traces/{run_id}`:读取单次运行 trace。
|
||||
- `GET /api/v1/agent-traces/conversations/{conversation_id}`:读取会话 trace。
|
||||
- 权限与校验:
|
||||
- 复用当前 API 依赖和系统登录态。
|
||||
- 不允许通过 trace 接口修改业务数据。
|
||||
- 持久化:
|
||||
- 新增 `agent_trace_events` 表。
|
||||
- 通过 `AgentTraceService` 封装记录、查询、合成旧数据时间线。
|
||||
|
||||
### 算法与规则
|
||||
|
||||
- 规则输入:Agent 运行阶段、工具调用结果、语义解析结果和会话消息。
|
||||
- 规则流程:
|
||||
- 采集阶段按固定事件名记录。
|
||||
- 查询阶段按事件序列合并 run、semantic、tool、conversation。
|
||||
- 无事件时从历史 run 数据合成 fallback timeline。
|
||||
- 结果解释:
|
||||
- 每个事件输出 `title`、`summary`、`status`、`duration_ms`、`input_json`、`output_json`、`error_message`。
|
||||
|
||||
## 算法与公式
|
||||
|
||||
当前功能不涉及评分、预算或风控公式,只涉及耗时统计:
|
||||
|
||||
$$
|
||||
duration\_ms = finished\_at - started\_at
|
||||
$$
|
||||
|
||||
变量说明:
|
||||
|
||||
- $duration\_ms$:阶段耗时,单位毫秒。
|
||||
- $finished\_at$:阶段结束时间。
|
||||
- $started\_at$:阶段开始时间。
|
||||
|
||||
## 测试方案
|
||||
|
||||
- 单元测试:覆盖 `AgentTraceService` 记录事件、查询详情、旧 run fallback 时间线。
|
||||
- 接口测试:覆盖 trace 列表、单 run 详情、会话详情。
|
||||
- 前端交互测试:覆盖 trace 数据归一化、状态文案、空态和错误态。
|
||||
- 端到端测试:通过一次 Orchestrator 用户消息生成 `run_id`,验证详情接口能返回语义解析、路由和至少一个事件。
|
||||
- 回归测试:确认原 `agent-runs` 接口、数字员工工作记录、系统日志详情不破坏。
|
||||
- 手工验证:在浏览器打开 Trace Center,检查列表、详情和 JSON 展示。
|
||||
|
||||
## 指标与验收
|
||||
|
||||
- [A1] 功能验收:一次 Orchestrator 调用后,能通过 `run_id` 查询到完整 trace 详情。
|
||||
- [A2] 性能指标:单次 trace 详情查询在常规数据量下不引入明显慢查询;默认列表限制数量。
|
||||
- [A3] 质量指标:后端定向测试在 Docker `x-financial-main` 容器内 60s 超时内通过。
|
||||
- [A4] 安全/权限指标:trace 接口只读,不触发业务动作或副作用。
|
||||
- [A5] 可观测性:失败运行也能看到最后成功事件和失败事件。
|
||||
|
||||
## 风险与开放问题
|
||||
|
||||
- 风险:当前工作树已有大量未提交改动,本轮实现必须避免覆盖既有业务改动。
|
||||
- 已处理依赖:新增 trace 模型已纳入 `Base` 导入,`AgentTraceService.ensure_storage_ready()` 会按需创建 trace 事件表。
|
||||
- 待确认:后续是否需要接 OpenTelemetry、跨容器 trace 或长期归档策略。
|
||||
- 降级策略:没有 trace event 的旧 run 通过 `semantic_parse`、`tool_calls` 和 `route_json` 合成只读时间线。
|
||||
|
||||
## 本轮实现记录
|
||||
|
||||
- 后端已完成 `AgentTraceEvent`、`AgentTraceService` 和 `/api/v1/agent-traces` 只读接口。
|
||||
- Orchestrator 已在接收请求、会话补全、语义识别、路由、工具调用、会话写回、最终回复和失败路径写入 trace event。
|
||||
- 前端已在系统设置中新增 Agent Trace Center,并从日志详情、数字员工工作记录跳转到指定 `run_id`。
|
||||
- 本轮保持非目标不变:不做真实业务重跑、不接 OpenTelemetry、不处理 GL/总账体系和前端统一状态管理。
|
||||
@@ -1,55 +0,0 @@
|
||||
# Agent链路追踪中心 开发 TODO
|
||||
|
||||
## 使用规则
|
||||
|
||||
- 每个 TODO 必须对应 `CONCEPT.md` 中的目标、能力或验收点。
|
||||
- 只有完成并验证后,才能把 `[ ]` 改成 `[x]`。
|
||||
- 勾选时在任务后补充简短证据,例如文件、接口、命令或验证结果。
|
||||
- 如果需求发生变化,先更新 `CONCEPT.md`,再调整本 TODO。
|
||||
|
||||
## 1. 调研与边界
|
||||
|
||||
- [x] [CONCEPT: 背景与问题] 阅读相关页面、接口、服务、测试和历史文档,记录当前实现事实。证据:已确认 `agent_runs`、`agent_tool_calls`、`semantic_parse_logs`、`LogDetailView`、`DigitalEmployeeWorkRecords` 现状。
|
||||
- [x] [CONCEPT: 目标与非目标] 确认本轮开发范围,写清楚不做项。证据:`CONCEPT.md` 明确只做历史重放,不做调试重跑和 OpenTelemetry。
|
||||
- [x] [CONCEPT: 风险与开放问题] 标记无法立即确认的依赖、风险和假设。证据:`CONCEPT.md` 风险章节记录脏工作树和数据库初始化依赖。
|
||||
|
||||
## 2. 契约与设计
|
||||
|
||||
- [x] [CONCEPT: 功能能力] 定义输入、输出、状态、权限和边界条件。证据:`CONCEPT.md` 功能能力章节。
|
||||
- [x] [CONCEPT: 方案设计] 明确前端、后端、算法、数据的职责边界。证据:`CONCEPT.md` 方案设计章节。
|
||||
- [x] [CONCEPT: 算法与公式] 补全耗时公式和变量解释。证据:`CONCEPT.md` 算法与公式章节。
|
||||
- [x] [CONCEPT: 指标与验收] 把验收标准转成可验证的检查点。证据:`CONCEPT.md` 指标与验收章节。
|
||||
|
||||
## 3. 后端实现
|
||||
|
||||
- [x] [CONCEPT: 后端] 新增 trace 事件模型、schema、repository/service。证据:`AgentTraceEvent`、`agent_trace.py`、`AgentTraceService`。
|
||||
- [x] [CONCEPT: 后端] 新增 `agent-traces` 只读接口和路由注册。证据:`agent_traces.py` endpoint 与 `router.py` 注册。
|
||||
- [x] [CONCEPT: 后端] 在 Orchestrator 关键节点写入 trace event。证据:`orchestrator.py` 记录接收、会话、语义、路由、回复、失败事件;`orchestrator_execution.py` 记录工具调用事件。
|
||||
- [x] [CONCEPT: 数据] 实现旧 run fallback 时间线,避免旧数据详情为空。证据:`AgentTraceService.get_trace()` 在无事件时由 `AgentRun`、`SemanticParseLog`、`AgentToolCall` 合成只读时间线。
|
||||
|
||||
## 4. 算法/规则实现
|
||||
|
||||
- [x] [CONCEPT: 算法与规则] 实现 trace 事件排序、耗时计算和状态归一化。证据:`AgentTraceService._next_sequence()`、`_resolve_duration_ms()`、`agentTraceViewModel.js`。
|
||||
- [x] [CONCEPT: 结果解释] 输出可读事件标题、摘要、输入输出和错误信息。证据:Trace event schema 与 `AgentTraceCenterView.vue` 详情面板。
|
||||
|
||||
## 5. 前端实现
|
||||
|
||||
- [x] [CONCEPT: 前端] 新增 trace 服务 API 和数据归一化工具。证据:`agentTraces.js`、`agentTraceViewModel.js`。
|
||||
- [x] [CONCEPT: 前端] 新增 Trace Center 列表与详情视图。证据:`AgentTraceCenterView.vue`。
|
||||
- [x] [CONCEPT: 前端] 从现有日志详情和工作记录补充 trace 跳转入口。证据:`LogDetailView.vue`、`DigitalEmployeeWorkRecords.vue`。
|
||||
- [x] [CONCEPT: 前端] 实现加载、空态、错误态和刷新。证据:`AgentTraceCenterView.vue` 列表/详情状态与刷新按钮。
|
||||
- [x] [CONCEPT: 前端] 对齐现有企业级直角、低饱和、密集信息风格。证据:`agent-trace-center-view.css` 使用面板、表格、状态徽标和紧凑信息布局。
|
||||
|
||||
## 6. 测试与验证
|
||||
|
||||
- [x] [CONCEPT: 测试方案] 补充后端 service/API 定向测试。证据:`test_agent_trace_service.py` 覆盖事件记录、fallback、接口列表和详情。
|
||||
- [x] [CONCEPT: 测试方案] 补充前端数据归一化测试或可构建验证。证据:`npm.cmd --prefix web run build` 通过。
|
||||
- [x] [CONCEPT: 测试方案] 在 60s 超时内运行 Docker 后端定向验证。证据:`docker exec ... pytest -q server/tests/test_agent_trace_service.py server/tests/test_agent_runs_service.py`,7 passed。
|
||||
- [x] [CONCEPT: 测试方案] 运行 `npm.cmd --prefix web run build`。证据:Vite build 成功。
|
||||
- [x] [CONCEPT: 指标与验收] 记录验证命令、结果和未覆盖风险。证据:后端测试 7 passed、Vite build 成功、重启后 `/api/v1/agent-traces/{run_id}` live 返回 8 个 fallback 事件;浏览器插件后续不可用,未完成最终截图巡检。
|
||||
|
||||
## 7. 文档收尾
|
||||
|
||||
- [x] [CONCEPT: 指标与验收] 回看所有验收点,确认均有实现或验证证据。证据:后端 service/API 测试、前端构建、入口接入均已完成。
|
||||
- [x] [CONCEPT: 风险与开放问题] 更新剩余风险、后续任务和明确不做项。证据:`CONCEPT.md` 保留 OpenTelemetry、跨容器 trace、长期归档为后续待定。
|
||||
- [x] [CONCEPT: 功能一句话] 确认最终实现没有偏离原始目标。证据:本轮只做 Agent Trace Center,未处理 GL/前端状态管理两项待定问题。
|
||||
@@ -1,515 +0,0 @@
|
||||
# 员工业务行为画像模型方案
|
||||
|
||||
## 目标
|
||||
|
||||
员工业务行为画像用于把费用申请、审批流转、AI 协作和数字员工巡检中产生的行为数据沉淀为可解释的统计画像。
|
||||
|
||||
它不是给员工贴负面标签,也不是替代审批人做最终判断,而是为以下场景提供结构化依据:
|
||||
|
||||
- 费用审批详情页展示申请人近期费用节奏和材料质量。
|
||||
- Hermes 数字员工定期巡检高频费用、异常预算占用和流程质量问题。
|
||||
- 运营看板观察 AI 使用、Token 消耗、流程耗时和审核效率。
|
||||
- 后续规则中心根据真实覆盖率和人工覆盖情况优化规则阈值。
|
||||
|
||||
## 设计原则
|
||||
|
||||
1. 不把不同性质的数据混成一个总分。
|
||||
2. 费用风险、流程质量、AI 使用、审批行为必须分维度计算。
|
||||
3. 画像结果必须能追溯到指标、窗口期、同组基准和计算时间。
|
||||
4. Hermes 负责调度和沉淀快照,确定性算法负责计算,LLM 只可用于解释和报告。
|
||||
5. 画像用于审批参考和运营治理,不直接作为惩罚或自动降标依据。
|
||||
|
||||
## 画像分层
|
||||
|
||||
```text
|
||||
员工业务行为画像
|
||||
├── 费用支出画像
|
||||
├── 流程质量画像
|
||||
├── AI 协作画像
|
||||
└── 审批行为画像
|
||||
```
|
||||
|
||||
### 费用支出画像
|
||||
|
||||
用于判断申请人的费用节奏是否显著高于同组基准。
|
||||
|
||||
核心指标:
|
||||
|
||||
- 近 30 / 90 / 180 天申请次数。
|
||||
- 近 30 / 90 / 180 天申请金额。
|
||||
- 差旅申请次数、出差天数、日均费用。
|
||||
- 招待申请次数、人均招待金额、同客户重复招待次数。
|
||||
- 个人费用占部门预算比例。
|
||||
- 个人费用占项目预算比例。
|
||||
- 同部门、同岗位、同费用类型分位数。
|
||||
- 历史调减、退回、复核次数。
|
||||
|
||||
审批用途:
|
||||
|
||||
- 识别高频费用申请人。
|
||||
- 提醒审核者复核出差天数和费用标准。
|
||||
- 推荐补充业务必要性、拆分费用或升级审批。
|
||||
|
||||
### 流程质量画像
|
||||
|
||||
用于判断申请人提交材料和流程配合质量。
|
||||
|
||||
核心指标:
|
||||
|
||||
- 草稿到提交平均耗时。
|
||||
- 退回到重新提交平均耗时。
|
||||
- 退单次数。
|
||||
- 补充材料次数。
|
||||
- 附件缺失次数。
|
||||
- 发票金额不一致次数。
|
||||
- 申请事由缺失次数。
|
||||
- 业务地点缺失次数。
|
||||
- 项目编号缺失次数。
|
||||
- 同一申请多次修改次数。
|
||||
|
||||
审批用途:
|
||||
|
||||
- 提示“近期材料质量偏低,需要重点核对附件和事由”。
|
||||
- 对高频退单申请人提高材料完整性检查权重。
|
||||
- 对低质量申请触发补充材料建议,而不是直接判定费用风险。
|
||||
|
||||
### AI 协作画像
|
||||
|
||||
用于观察员工和系统的 AI 协作行为,不直接判定费用风险。
|
||||
|
||||
核心指标:
|
||||
|
||||
- AI 调用次数。
|
||||
- AI 辅助生成申请次数。
|
||||
- AI 解析票据次数。
|
||||
- AI 预审次数。
|
||||
- 语义解析次数。
|
||||
- 输入 Token。
|
||||
- 输出 Token。
|
||||
- 总 Token。
|
||||
- 估算调用成本。
|
||||
- AI 建议被采纳次数。
|
||||
- AI 建议被人工覆盖次数。
|
||||
- AI 生成后人工修改次数。
|
||||
|
||||
运营用途:
|
||||
|
||||
- 观察哪些流程高度依赖 AI。
|
||||
- 识别高成本用户、部门或功能入口。
|
||||
- 衡量 AI 建议采纳率和被覆盖率。
|
||||
- 为模型配置、成本控制和产品优化提供依据。
|
||||
|
||||
审批边界:
|
||||
|
||||
AI 使用多不等于风险高。Token 消耗、AI 调用次数不应直接推高费用审批风险,只能作为运营和辅助说明。
|
||||
|
||||
### 审批行为画像
|
||||
|
||||
用于分析审批人的审核效率和审核风格。
|
||||
|
||||
核心指标:
|
||||
|
||||
- 平均审核时长。
|
||||
- 中位审核时长。
|
||||
- 超 SLA 次数。
|
||||
- 直接通过率。
|
||||
- 退回率。
|
||||
- 调减率。
|
||||
- 高风险单据通过率。
|
||||
- 系统建议采纳率。
|
||||
- 系统建议覆盖率。
|
||||
- 审批意见完整度。
|
||||
- 审批积压数量。
|
||||
|
||||
治理用途:
|
||||
|
||||
- 识别审批积压。
|
||||
- 识别过度宽松或过度退回的审批模式。
|
||||
- 评估规则建议是否被人工持续覆盖。
|
||||
- 为流程优化和审批授权调整提供依据。
|
||||
|
||||
## 计算窗口
|
||||
|
||||
第一版建议支持三个窗口:
|
||||
|
||||
```text
|
||||
30 天:识别近期异常波动
|
||||
90 天:作为审批详情页默认画像
|
||||
180 天:用于稳定趋势和年度预算节奏
|
||||
```
|
||||
|
||||
审批详情页默认读取 `90 天` 画像。运营看板可以切换 30 / 90 / 180 天。
|
||||
|
||||
## 同组基准
|
||||
|
||||
费用支出画像必须和可比人群比较,不能全公司一刀切。
|
||||
|
||||
建议同组口径:
|
||||
|
||||
```text
|
||||
peer_group =
|
||||
department_id
|
||||
+ position
|
||||
+ grade
|
||||
+ expense_type_scope
|
||||
+ city_tier
|
||||
+ project_type
|
||||
+ window_days
|
||||
```
|
||||
|
||||
当某个同组样本量不足时,逐级回退:
|
||||
|
||||
```text
|
||||
部门 + 岗位 + 费用类型
|
||||
→ 部门 + 费用类型
|
||||
→ 岗位 + 费用类型
|
||||
→ 公司 + 费用类型
|
||||
```
|
||||
|
||||
回退必须写入 `peer_group_fallback_level`,避免审核者误以为基准非常精确。
|
||||
|
||||
## 分值模型
|
||||
|
||||
### 不建议使用一个大总分
|
||||
|
||||
不要这样计算:
|
||||
|
||||
```text
|
||||
综合风险分 = 费用金额 + Token 消耗 + 操作时长 + 审核时长 + 退单次数
|
||||
```
|
||||
|
||||
原因:
|
||||
|
||||
- Token 高可能代表高频使用 AI,不代表费用风险。
|
||||
- 审核时长是审批人的行为,不是申请人的费用风险。
|
||||
- 退单次数可能代表材料质量问题,不一定代表费用不合理。
|
||||
- 一个总分会掩盖到底是哪一类风险触发。
|
||||
|
||||
### 建议使用多维分
|
||||
|
||||
```text
|
||||
employee_behavior_profile =
|
||||
expense_profile_score
|
||||
process_quality_score
|
||||
ai_usage_score
|
||||
approval_behavior_score
|
||||
```
|
||||
|
||||
每个分值都有自己的等级:
|
||||
|
||||
```text
|
||||
0-39 normal
|
||||
40-59 watch
|
||||
60-79 review
|
||||
80-100 escalation
|
||||
```
|
||||
|
||||
审批详情页只展示与当前场景相关的分值:
|
||||
|
||||
```text
|
||||
费用申请审批:
|
||||
展示 expense_profile_score
|
||||
展示 process_quality_score
|
||||
隐藏或弱化 ai_usage_score
|
||||
不展示 approval_behavior_score
|
||||
|
||||
运营看板:
|
||||
展示四类分值和趋势
|
||||
```
|
||||
|
||||
## 指标权重建议
|
||||
|
||||
### 费用支出画像分
|
||||
|
||||
```text
|
||||
expense_profile_score =
|
||||
frequency_score * 20%
|
||||
+ amount_occupancy_score * 25%
|
||||
+ peer_deviation_score * 25%
|
||||
+ adjustment_history_score * 15%
|
||||
+ current_claim_deviation_score * 15%
|
||||
```
|
||||
|
||||
### 流程质量画像分
|
||||
|
||||
```text
|
||||
process_quality_score =
|
||||
return_count_score * 25%
|
||||
+ missing_attachment_score * 20%
|
||||
+ invoice_mismatch_score * 20%
|
||||
+ resubmit_duration_score * 15%
|
||||
+ missing_business_context_score * 20%
|
||||
```
|
||||
|
||||
### AI 协作画像分
|
||||
|
||||
AI 协作分不命名为风险分,建议叫 `ai_usage_intensity_score`。
|
||||
|
||||
```text
|
||||
ai_usage_intensity_score =
|
||||
ai_call_count_score * 25%
|
||||
+ token_cost_score * 25%
|
||||
+ ai_generated_claim_ratio_score * 20%
|
||||
+ ai_suggestion_override_score * 20%
|
||||
+ failed_ai_call_score * 10%
|
||||
```
|
||||
|
||||
含义:
|
||||
|
||||
- 分数高代表 AI 使用强度高或成本高。
|
||||
- 不代表员工费用风险高。
|
||||
- 主要用于成本治理、流程优化和模型配置。
|
||||
|
||||
### 审批行为画像分
|
||||
|
||||
审批行为分不命名为风险分,建议叫 `approval_behavior_score`。
|
||||
|
||||
```text
|
||||
approval_behavior_score =
|
||||
avg_review_duration_score * 20%
|
||||
+ sla_overdue_score * 20%
|
||||
+ direct_approve_ratio_score * 20%
|
||||
+ high_risk_approve_score * 20%
|
||||
+ system_advice_override_score * 20%
|
||||
```
|
||||
|
||||
含义:
|
||||
|
||||
- 分数高代表审批行为需要运营关注。
|
||||
- 不直接代表审批人存在问题。
|
||||
- 必须结合审批量、单据复杂度和部门业务特性解释。
|
||||
|
||||
## 数据来源
|
||||
|
||||
### 费用与流程数据
|
||||
|
||||
主要来源:
|
||||
|
||||
- `expense_claims`
|
||||
- `expense_claim_items`
|
||||
- 审批流转记录
|
||||
- 退回 / 调减 / 补充材料记录
|
||||
- 预算池和预算交易记录
|
||||
|
||||
需要补齐或确认的数据:
|
||||
|
||||
- 审批开始时间。
|
||||
- 审批完成时间。
|
||||
- 退回原因结构化字段。
|
||||
- 调减前后金额。
|
||||
- 补充材料事件。
|
||||
- 审批意见是否为空。
|
||||
|
||||
### AI 与工具调用数据
|
||||
|
||||
主要来源:
|
||||
|
||||
- `AgentRun`
|
||||
- `AgentToolCall`
|
||||
- `SemanticParseLog`
|
||||
- `runtime_chat.py`
|
||||
- `ontology.py`
|
||||
- `user_agent.py`
|
||||
- `ocr.py`
|
||||
|
||||
需要注意:
|
||||
|
||||
不是所有模型入口都已经完整持久化 Token。第一版必须区分:
|
||||
|
||||
```text
|
||||
exact_token_count:真实记录的 Token
|
||||
estimated_token_count:按文本长度估算
|
||||
unavailable:当前不可用
|
||||
```
|
||||
|
||||
不能把估算值包装成真实计费数据。
|
||||
|
||||
## 存储设计
|
||||
|
||||
建议第一版使用通用画像快照表:
|
||||
|
||||
```text
|
||||
employee_behavior_profile_snapshots
|
||||
```
|
||||
|
||||
字段建议:
|
||||
|
||||
```text
|
||||
id
|
||||
subject_type applicant / approver / employee
|
||||
subject_id employee_id
|
||||
subject_name
|
||||
department_id
|
||||
department_name
|
||||
position
|
||||
grade
|
||||
|
||||
profile_type expense / process_quality / ai_usage / approval
|
||||
window_days 30 / 90 / 180
|
||||
expense_type_scope overall / travel / entertainment / ...
|
||||
peer_group_key
|
||||
peer_group_fallback_level
|
||||
|
||||
profile_score
|
||||
profile_level
|
||||
metrics_json
|
||||
basis_codes_json
|
||||
source_task_type
|
||||
source_task_log_id
|
||||
calculated_at
|
||||
created_at
|
||||
```
|
||||
|
||||
### 为什么用快照表
|
||||
|
||||
不要把画像直接写入员工表:
|
||||
|
||||
```text
|
||||
employee.profile_score = 80
|
||||
```
|
||||
|
||||
原因:
|
||||
|
||||
- 员工表是主数据,画像是动态计算结果。
|
||||
- 审批审计需要知道当时为什么是这个分。
|
||||
- 算法规则调整后,历史依据不能被覆盖。
|
||||
- 快照可以支持趋势分析。
|
||||
|
||||
### 是否每个员工都存
|
||||
|
||||
不建议全员每天存。
|
||||
|
||||
第一版只存:
|
||||
|
||||
- 近 90 / 180 天有费用申请记录的员工。
|
||||
- 当前存在待审批申请的员工。
|
||||
- 上一期画像等级为 `watch`、`review`、`escalation` 的员工。
|
||||
- AI 使用或审批行为达到运营关注阈值的员工。
|
||||
|
||||
无行为员工不生成画像快照。
|
||||
|
||||
## Hermes 调度策略
|
||||
|
||||
不重新写调度器,复用 Hermes 现有 cron 调度体系。
|
||||
|
||||
建议新增任务类型:
|
||||
|
||||
```text
|
||||
employee_behavior_profile_scan
|
||||
```
|
||||
|
||||
任务职责:
|
||||
|
||||
```text
|
||||
1. 识别本次需要刷新画像的员工集合。
|
||||
2. 聚合费用、流程、AI、审批行为指标。
|
||||
3. 调用各画像子算法。
|
||||
4. 写入 employee_behavior_profile_snapshots。
|
||||
5. 在 HermesTaskExecutionLog 写入执行摘要。
|
||||
```
|
||||
|
||||
建议频率:
|
||||
|
||||
```text
|
||||
事件触发:申请提交、审批完成、退回、调减、AI 任务完成后,刷新相关员工。
|
||||
每日轻量:只扫描昨日新增行为和上一期高关注员工。
|
||||
每周全量:刷新同组基准、分位数和活跃员工画像。
|
||||
每月复盘:分析阈值、规则覆盖率和人工覆盖率。
|
||||
```
|
||||
|
||||
## 审批详情展示
|
||||
|
||||
费用审批详情页建议展示:
|
||||
|
||||
```text
|
||||
申请人费用画像
|
||||
流程材料质量
|
||||
本次申请实时偏离
|
||||
```
|
||||
|
||||
不建议在普通审批详情页直接展示:
|
||||
|
||||
```text
|
||||
Token 消耗
|
||||
AI 调用成本
|
||||
审批人行为分
|
||||
```
|
||||
|
||||
这些更适合管理员运营看板。
|
||||
|
||||
示例展示:
|
||||
|
||||
```text
|
||||
申请人费用画像
|
||||
近 90 天 · 销售部 / 客户经理 / 差旅费
|
||||
状态:重点复核
|
||||
|
||||
触发依据:
|
||||
- 近 90 天差旅金额处于同组 P88。
|
||||
- 本次出差天数为同类 P75 的 1.67 倍。
|
||||
- 最近 180 天存在 3 次调减或退回记录。
|
||||
|
||||
审核建议:
|
||||
- 建议确认本次 5 天行程是否可压缩至 4 天。
|
||||
- 如确属关键客户推进,请补充客户拜访安排和预期产出。
|
||||
```
|
||||
|
||||
## 运营看板展示
|
||||
|
||||
管理员或运营人员可以看到更完整的画像:
|
||||
|
||||
```text
|
||||
员工画像总览
|
||||
├── 费用支出关注榜
|
||||
├── 流程质量待优化榜
|
||||
├── AI 使用强度榜
|
||||
├── Token 成本趋势
|
||||
├── 审批效率与积压
|
||||
└── 系统建议采纳率
|
||||
```
|
||||
|
||||
运营看板要标明:
|
||||
|
||||
- 哪些指标是真实采集。
|
||||
- 哪些指标是估算。
|
||||
- 哪些指标当前不可用。
|
||||
|
||||
## 第一版落地边界
|
||||
|
||||
第一版建议先做:
|
||||
|
||||
1. 费用支出画像。
|
||||
2. 流程质量画像。
|
||||
3. AI 协作画像的数据口径定义。
|
||||
4. 通用快照表。
|
||||
5. Hermes 画像扫描任务。
|
||||
|
||||
暂不做:
|
||||
|
||||
- 自动处罚或自动降标。
|
||||
- 将 AI Token 消耗纳入费用风险分。
|
||||
- 用 LLM 直接判断员工是否异常。
|
||||
- 全员每日全量画像。
|
||||
|
||||
## 后续演进
|
||||
|
||||
### 第二阶段
|
||||
|
||||
- 接入审批详情页“申请人费用画像”卡片。
|
||||
- 接入 Hermes 数字员工日志。
|
||||
- 支持画像快照趋势对比。
|
||||
- 支持规则中心根据高频触发指标生成规则草稿。
|
||||
|
||||
### 第三阶段
|
||||
|
||||
- 引入更稳定的同组基准缓存。
|
||||
- 引入审批建议采纳率。
|
||||
- 对 AI 使用成本做部门和功能维度分摊。
|
||||
- 将画像结果接入运营看板。
|
||||
|
||||
### 第四阶段
|
||||
|
||||
- 根据真实历史数据调整权重。
|
||||
- 对高覆盖、高误报规则做自动复盘。
|
||||
- 让 Hermes 输出月度费用治理建议,但仍不直接改线上规则。
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,681 +0,0 @@
|
||||
# 员工业务行为画像功能概念文档
|
||||
|
||||
## 1. 功能一句话
|
||||
|
||||
员工业务行为画像通过确定性算法把费用申请、流程质量、AI 协作和审批行为沉淀为可追溯的画像快照,并在审批详情、Hermes 数字员工巡检和运营看板中提供可解释的审核依据。
|
||||
|
||||
## 2. 背景与问题
|
||||
|
||||
预算费用规划推荐模型需要解释“为什么某个申请应该被重点审核”。仅看当前单据金额不够,因为同样的金额在不同员工、部门、岗位、城市和费用类型下含义不同。
|
||||
|
||||
当前讨论中已经明确几个问题:
|
||||
|
||||
- 出差天数、出差金额、业务招待频次和招待标准需要和申请人挂钩,否则审核者看不到长期费用节奏。
|
||||
- 用户操作时长、AI 使用次数、Token 消耗、审核时长、退单次数等指标也有价值,但它们性质不同,不能混成一个“坏人分”。
|
||||
- 审批详情需要一个直观入口展示画像,例如“风险审核画像”卡片,但卡片必须展示证据、口径和建议,避免给员工贴不可解释标签。
|
||||
- Hermes 已有数字员工和调度入口,画像检测应该接入现有 Hermes 任务体系,而不是另写一套调度器。
|
||||
|
||||
代码现状可作为第一版基础:
|
||||
|
||||
- `AgentRun`、`AgentToolCall`、`SemanticParseLog` 已记录 Agent 运行、工具调用耗时和语义解析日志。
|
||||
- `ExpenseClaim`、`ExpenseClaimItem` 已承载费用申请和明细。
|
||||
- `HermesTaskConfig`、`HermesTaskExecutionLog` 已承载 Hermes 任务配置和执行日志。
|
||||
- 现有 Hermes 调度器会轮询启用任务,并按 `task_type` 分发到具体服务。
|
||||
- 当前前端 Hermes 设置仅暴露 `global_risk_scan` 和 `weekly_expense_report` 两类任务,画像任务需要补齐配置入口。
|
||||
|
||||
## 3. 目标与非目标
|
||||
|
||||
### 3.1 目标
|
||||
|
||||
- 建立员工维度的多层画像:费用支出画像、流程质量画像、AI 协作画像、审批行为画像。
|
||||
- 建立可审计的快照存储,不把动态画像直接写进员工主表。
|
||||
- 形成可解释的量化公式,支持 30 / 90 / 180 天窗口。
|
||||
- 接入 Hermes 数字员工任务,定期生成画像快照和汇总日志。
|
||||
- 在审批详情中展示“风险审核画像”卡片,默认突出费用支出和流程质量。
|
||||
- 保留指标来源、同组基准、计算窗口、任务日志和算法版本,便于复核。
|
||||
- 明确 Token 统计口径:真实值、估算值和不可用值必须区分。
|
||||
|
||||
### 3.2 非目标
|
||||
|
||||
- 不用画像自动处罚员工,也不自动降低费用标准或缩短出差天数。
|
||||
- 不把 AI 使用次数、Token 消耗直接当作费用风险。
|
||||
- 不做全员每日全量画像快照,避免频率过高和无意义存储。
|
||||
- 不重写 Hermes 调度器;如频率能力不足,优先增强现有 Hermes 调度体系。
|
||||
- 不用 LLM 直接判定风险等级;LLM 仅可用于解释、摘要和报告生成。
|
||||
|
||||
## 4. 用户与场景
|
||||
|
||||
### 4.1 费用审核者
|
||||
|
||||
在费用申请详情页查看“风险审核画像”卡片。审核者需要知道:
|
||||
|
||||
- 申请人近期是否频繁申请大额出差或招待。
|
||||
- 当前申请是否显著高于同组基准或个人历史。
|
||||
- 申请人的材料质量是否经常导致退单、补充材料或复核。
|
||||
- 系统建议是“重点复核”“建议补充说明”还是“建议升级审批”。
|
||||
|
||||
### 4.2 财务和预算管理员
|
||||
|
||||
在运营看板或 Hermes 报告中查看部门、项目、费用类型下的画像趋势。管理员需要识别:
|
||||
|
||||
- 哪些部门或项目存在持续预算占用压力。
|
||||
- 哪些费用类型的人均标准偏离明显。
|
||||
- 哪些流程环节反复出现退单或材料缺失。
|
||||
|
||||
### 4.3 AI 运营人员
|
||||
|
||||
观察 AI 调用、Token 消耗、建议采纳率和覆盖率。AI 运营人员需要知道:
|
||||
|
||||
- 哪些入口消耗高但采纳率低。
|
||||
- 哪些业务流程高度依赖 AI。
|
||||
- 哪些模型调用需要限额、优化或替换。
|
||||
|
||||
### 4.4 Hermes 数字员工
|
||||
|
||||
Hermes 作为调度入口,负责在设定周期内触发画像计算、写入快照、记录执行日志,并输出可读摘要。
|
||||
|
||||
## 5. 功能能力
|
||||
|
||||
### 5.1 输入
|
||||
|
||||
- 费用申请:申请人、部门、岗位、费用类型、申请金额、审批金额、出差天数、招待客户、业务地点、项目编号。
|
||||
- 费用明细:明细金额、票据金额、费用类型、发生日期、供应商或客户线索。
|
||||
- 审批流转:提交时间、审核开始时间、审核完成时间、退单、调减、复核、审批意见。
|
||||
- Agent 数据:Agent 运行记录、工具调用次数、工具耗时、语义解析、AI 建议、AI 建议采纳或覆盖。
|
||||
- Token 数据:输入 Token、输出 Token、总 Token、估算 Token、不可用状态。
|
||||
- Hermes 数据:任务配置、任务执行日志、报告或风险巡检结果。
|
||||
- 组织基准:部门、岗位、职级、城市等级、项目类型、费用类型和预算池。
|
||||
|
||||
### 5.2 输出
|
||||
|
||||
- 员工画像快照:每个员工、每个窗口、每个画像类型一条或多条快照。
|
||||
- 最新画像查询:给审批详情、运营看板和 Hermes 报告读取。
|
||||
- 画像证据:指标值、同组基准、贡献项、命中原因、数据质量标记。
|
||||
- 画像标签:把复杂指标转成可读标签,例如“费用之王”“长差达人”“材料补丁户”“急速审核员”,每个标签必须有触发公式、置信度和证据。
|
||||
- 行为雷达图:把费用、差旅招待、流程质量、AI 协作和审批行为压缩成 6 到 8 个维度,用于分析者快速理解员工行为结构。
|
||||
- 审核建议:复核天数、复核金额、补充材料、升级审批、关注预算占用等建议。
|
||||
- Hermes 执行摘要:本次计算人数、生成快照数、高关注人数、失败原因。
|
||||
|
||||
### 5.3 审批详情卡片
|
||||
|
||||
审批详情中建议新增卡片:`风险审核画像`。
|
||||
|
||||
卡片默认展示:
|
||||
|
||||
- 总览:画像等级、计算时间、窗口期、同组基准口径。
|
||||
- 特征标签:展示 3 到 6 个置信度最高、与当前场景相关的标签;风险型标签优先,但必须保留证据入口。
|
||||
- 雷达图:展示行为维度得分,帮助审核者一眼判断该员工是“费用强度高”“材料质量弱”还是“审批节奏快”。
|
||||
- 费用支出:频次、金额占用、同组偏离、历史调减、当前单据偏离。
|
||||
- 流程质量:退单、附件缺失、发票不一致、补充材料、重提耗时。
|
||||
- 当前单据建议:是否建议复核出差天数、招待人均金额、业务必要性或预算占用。
|
||||
- 证据展开:展示贡献最高的 3 到 5 个指标和原始口径。
|
||||
|
||||
审批详情默认不突出 AI 协作画像和审批人行为画像。AI 指标主要服务运营治理,审批人画像只在管理员或流程治理场景展示。
|
||||
|
||||
### 5.4 权限和边界
|
||||
|
||||
- 普通审核者只能看到与当前单据审核有关的申请人费用画像和流程质量画像。
|
||||
- 财务管理员可查看部门、项目和费用类型维度的汇总趋势。
|
||||
- AI 运营人员可查看 AI 协作画像,但不把它用于单据费用风险裁决。
|
||||
- 审批行为画像只面向管理员和流程治理角色展示。
|
||||
- 所有画像结论必须展示数据窗口和计算时间,避免被误读为永久标签。
|
||||
|
||||
## 6. 方案设计
|
||||
|
||||
### 6.1 数据模型
|
||||
|
||||
第一版建议新增通用快照表:
|
||||
|
||||
```text
|
||||
employee_behavior_profile_snapshots
|
||||
```
|
||||
|
||||
核心字段:
|
||||
|
||||
```text
|
||||
id
|
||||
subject_type applicant / approver / employee
|
||||
subject_id employee_id 或 user_id
|
||||
subject_name
|
||||
department_id
|
||||
department_name
|
||||
position
|
||||
grade
|
||||
|
||||
profile_type expense / process_quality / ai_usage / approval
|
||||
window_days 30 / 90 / 180
|
||||
expense_type_scope overall / travel / entertainment / ...
|
||||
peer_group_key
|
||||
peer_group_fallback_level
|
||||
|
||||
profile_score 0-100
|
||||
profile_level normal / watch / review / escalation
|
||||
metrics_json 原始指标、分位数、样本量、Token 口径
|
||||
basis_codes_json 贡献项和解释编码
|
||||
profile_tags_json 标签、触发分、置信度、证据和展示优先级
|
||||
radar_json 雷达图维度、维度分、维度等级和主导标签
|
||||
source_task_type employee_behavior_profile_scan
|
||||
source_task_log_id HermesTaskExecutionLog.id
|
||||
algorithm_version
|
||||
calculated_at
|
||||
created_at
|
||||
```
|
||||
|
||||
不建议把画像直接写入员工主表,例如 `employee.profile_score = 80`。画像是动态计算结果,需要保留算法版本、窗口期和历史依据。
|
||||
|
||||
### 6.2 后端服务
|
||||
|
||||
建议拆成三个职责:
|
||||
|
||||
- 数据抽取服务:从费用、审批、Agent、Hermes 记录中抽取指标。
|
||||
- 算法服务:在 `server/src/app/algorithem` 下维护评分公式、等级判定和解释贡献项。
|
||||
- 应用服务:负责员工集合筛选、快照写入、最新画像查询和 Hermes 执行结果汇总。
|
||||
|
||||
候选模块:
|
||||
|
||||
```text
|
||||
server/src/app/algorithem/employee_behavior_profile.py
|
||||
server/src/app/services/employee_behavior_profile_service.py
|
||||
server/src/app/services/hermes_employee_profile_scanner.py
|
||||
server/src/app/models/employee_behavior_profile.py
|
||||
```
|
||||
|
||||
### 6.3 Hermes 接入
|
||||
|
||||
新增任务类型:
|
||||
|
||||
```text
|
||||
employee_behavior_profile_scan
|
||||
```
|
||||
|
||||
接入原则:
|
||||
|
||||
- 复用现有 `HermesTaskConfig` 和 `HermesTaskExecutionLog`。
|
||||
- 在现有 `HermesScheduler._execute_task()` 中增加任务分发。
|
||||
- 在 `start_hermes_daemon.py` 中初始化画像任务配置。
|
||||
- 在 `hermesEmployeeSettingsModel.js` 中补充任务展示和默认开关。
|
||||
- 不创建第二个后台调度器。
|
||||
|
||||
频率建议:
|
||||
|
||||
- 第一版不做全员每日全量。
|
||||
- 推荐每周一次全量画像,工作日对存在待审单据的员工做轻量增量。
|
||||
- 如果现有 Hermes 调度只支持近似每日触发,应先把画像任务默认关闭或仅启用轻量扫描;后续在现有调度器内补齐 frequency / weekday / time 判断。
|
||||
|
||||
### 6.4 API 契约
|
||||
|
||||
审批详情读取最新画像:
|
||||
|
||||
```text
|
||||
GET /api/v1/employee-profiles/{employee_id}/latest
|
||||
```
|
||||
|
||||
建议查询参数:
|
||||
|
||||
```text
|
||||
scene=approval
|
||||
claim_id=<claim_id>
|
||||
window_days=90
|
||||
expense_type_scope=travel|entertainment|overall
|
||||
```
|
||||
|
||||
响应结构建议:
|
||||
|
||||
```json
|
||||
{
|
||||
"employee_id": "EMP001",
|
||||
"window_days": 90,
|
||||
"calculated_at": "2026-05-28T10:30:00+08:00",
|
||||
"peer_group": {
|
||||
"key": "FINANCE|M2|travel|tier1",
|
||||
"fallback_level": 1,
|
||||
"sample_size": 42
|
||||
},
|
||||
"profiles": [
|
||||
{
|
||||
"profile_type": "expense",
|
||||
"score": 72,
|
||||
"level": "review",
|
||||
"top_contributors": [
|
||||
{
|
||||
"code": "peer_deviation_high",
|
||||
"label": "差旅日均费用高于同组 P90",
|
||||
"value": 1.18,
|
||||
"unit": "ratio"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"profile_tags": [
|
||||
{
|
||||
"code": "expense_king",
|
||||
"label": "费用之王",
|
||||
"display_label": "费用集中度高",
|
||||
"category": "expense",
|
||||
"polarity": "risk",
|
||||
"score": 86,
|
||||
"confidence": 0.82,
|
||||
"reason": "近90天费用总额达到同组P90,且部门费用占比为34%",
|
||||
"metrics": {
|
||||
"amount_total": 128000,
|
||||
"peer_amount_p90": 76000,
|
||||
"amount_share": 0.34
|
||||
}
|
||||
}
|
||||
],
|
||||
"radar": {
|
||||
"dimensions": [
|
||||
{
|
||||
"code": "expense_intensity",
|
||||
"label": "费用强度",
|
||||
"score": 78,
|
||||
"level": "review",
|
||||
"top_tags": ["expense_king", "large_amount_deviation"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"review_suggestions": [
|
||||
{
|
||||
"type": "review_travel_days",
|
||||
"severity": "medium",
|
||||
"message": "建议复核出差天数和业务必要性"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 6.5 前端展示
|
||||
|
||||
审批详情页新增 `风险审核画像` 卡片,建议分成三层:
|
||||
|
||||
- 顶部摘要:等级、窗口期、同组基准、更新时间。
|
||||
- 中部指标:费用支出和流程质量两个分组。
|
||||
- 底部建议:系统建议和证据展开。
|
||||
|
||||
文案边界:
|
||||
|
||||
- 使用“关注”“复核”“建议”而不是“惩罚”“违规”“头号人物”。
|
||||
- 展示“该结论来自 90 天窗口和同组对比”,避免变成员工永久标签。
|
||||
- AI 协作强度只作为运营指标,不在费用审批默认卡片中强调。
|
||||
|
||||
## 7. 算法与公式
|
||||
|
||||
### 7.1 通用归一化
|
||||
|
||||
对越大越需要关注的指标,使用同组分位数归一化:
|
||||
|
||||
$$
|
||||
score(x) = clip\left(100 \times \frac{x - P_{50}}{P_{90} - P_{50}}, 0, 100\right)
|
||||
$$
|
||||
|
||||
其中:
|
||||
|
||||
- \(x\):员工在窗口期内的指标值。
|
||||
- \(P_{50}\):同组中位数。
|
||||
- \(P_{90}\):同组 90 分位数。
|
||||
- \(clip(v, 0, 100)\):把结果限制在 0 到 100。
|
||||
|
||||
当同组样本不足时,按以下顺序降级:
|
||||
|
||||
```text
|
||||
部门 + 岗位 + 费用类型
|
||||
→ 部门 + 费用类型
|
||||
→ 岗位 + 费用类型
|
||||
→ 公司 + 费用类型
|
||||
```
|
||||
|
||||
降级层级必须写入 `peer_group_fallback_level`。
|
||||
|
||||
### 7.2 费用支出画像
|
||||
|
||||
$$
|
||||
expense\_profile\_score =
|
||||
0.20F + 0.25B + 0.25D + 0.15H + 0.15C
|
||||
$$
|
||||
|
||||
变量定义:
|
||||
|
||||
- \(F\):费用申请频次分,包含出差、招待等申请次数。
|
||||
- \(B\):预算占用分,包含个人费用占部门或项目预算比例。
|
||||
- \(D\):同组偏离分,包含金额、天数、人均招待金额等分位数偏离。
|
||||
- \(H\):历史调减和复核分,包含历史调减、退回、复核次数。
|
||||
- \(C\):当前单据偏离分,衡量当前申请相对个人历史和同组基准的偏离。
|
||||
|
||||
### 7.3 流程质量画像
|
||||
|
||||
$$
|
||||
process\_quality\_score =
|
||||
0.25R + 0.20A + 0.20I + 0.15T + 0.20M
|
||||
$$
|
||||
|
||||
变量定义:
|
||||
|
||||
- \(R\):退单次数分。
|
||||
- \(A\):附件缺失分。
|
||||
- \(I\):发票金额或票据一致性问题分。
|
||||
- \(T\):退回后重新提交耗时分。
|
||||
- \(M\):业务上下文缺失分,包含事由、地点、项目编号、客户信息等。
|
||||
|
||||
### 7.4 AI 协作画像
|
||||
|
||||
AI 协作画像命名为强度分,不命名为风险分。
|
||||
|
||||
$$
|
||||
ai\_usage\_intensity\_score =
|
||||
0.25N + 0.25K + 0.20G + 0.20O + 0.10E
|
||||
$$
|
||||
|
||||
变量定义:
|
||||
|
||||
- \(N\):AI 调用次数分。
|
||||
- \(K\):Token 或估算成本分。
|
||||
- \(G\):AI 辅助生成申请比例分。
|
||||
- \(O\):AI 建议被人工覆盖分。
|
||||
- \(E\):AI 调用失败或低置信度分。
|
||||
|
||||
Token 口径必须进入 `metrics_json`:
|
||||
|
||||
```text
|
||||
exact_token_count 真实记录
|
||||
estimated_token_count 按文本长度估算
|
||||
unavailable 当前入口不可用
|
||||
```
|
||||
|
||||
### 7.5 审批行为画像
|
||||
|
||||
审批行为画像用于流程治理,不用于评价申请人的费用合理性。
|
||||
|
||||
$$
|
||||
approval\_behavior\_score =
|
||||
0.20L + 0.20S + 0.20P + 0.20Q + 0.20V
|
||||
$$
|
||||
|
||||
变量定义:
|
||||
|
||||
- \(L\):平均审核时长分。
|
||||
- \(S\):SLA 超时分。
|
||||
- \(P\):直接通过率异常分。
|
||||
- \(Q\):高风险单据通过率分。
|
||||
- \(V\):系统建议被覆盖分。
|
||||
|
||||
### 7.6 审批优先级分
|
||||
|
||||
审批详情只使用费用支出和流程质量形成优先级,不引入 AI 协作强度。
|
||||
|
||||
$$
|
||||
review\_priority\_score =
|
||||
clip(0.70 \times expense\_profile\_score +
|
||||
0.30 \times process\_quality\_score, 0, 100)
|
||||
$$
|
||||
|
||||
等级映射:
|
||||
|
||||
$$
|
||||
level(s)=
|
||||
\begin{cases}
|
||||
normal, & 0 \le s < 40 \\
|
||||
watch, & 40 \le s < 60 \\
|
||||
review, & 60 \le s < 80 \\
|
||||
escalation, & 80 \le s \le 100
|
||||
\end{cases}
|
||||
$$
|
||||
|
||||
### 7.7 审核建议公式
|
||||
|
||||
系统建议只能作为复核提示,不自动改写申请单。
|
||||
|
||||
差旅天数建议上限:
|
||||
|
||||
$$
|
||||
recommended\_days\_upper =
|
||||
min(requested\_days,\ P_{75}^{peer\_days} \times factor(level))
|
||||
$$
|
||||
|
||||
业务招待人均金额建议上限:
|
||||
|
||||
$$
|
||||
recommended\_entertainment\_unit\_upper =
|
||||
min(policy\_limit,\ P_{75}^{peer\_unit\_amount} \times factor(level))
|
||||
$$
|
||||
|
||||
其中:
|
||||
|
||||
$$
|
||||
factor(level)=
|
||||
\begin{cases}
|
||||
1.20, & normal \\
|
||||
1.10, & watch \\
|
||||
1.00, & review \\
|
||||
0.90, & escalation
|
||||
\end{cases}
|
||||
$$
|
||||
|
||||
如果当前申请本身有充分业务依据,审核者可以覆盖系统建议。覆盖原因应进入后续流程治理指标。
|
||||
|
||||
### 7.8 目标员工集合
|
||||
|
||||
第一版不计算全员。每次 Hermes 扫描目标集合为:
|
||||
|
||||
$$
|
||||
target\_employees =
|
||||
E_{claims180} \cup E_{pending} \cup E_{previous\_attention} \cup E_{ops\_threshold}
|
||||
$$
|
||||
|
||||
变量定义:
|
||||
|
||||
- \(E_{claims180}\):近 180 天有费用申请的员工。
|
||||
- \(E_{pending}\):当前有待审费用申请的员工。
|
||||
- \(E_{previous\_attention}\):上一期画像等级为 watch、review 或 escalation 的员工。
|
||||
- \(E_{ops\_threshold}\):AI 使用或审批行为达到运营关注阈值的员工。
|
||||
|
||||
### 7.9 用户画像标签体系
|
||||
|
||||
标签用于把复杂指标转成直观特征。标签不是永久评价,也不是处罚依据;它只表示员工在某个时间窗口、某个同组基准下呈现出的行为特征。
|
||||
|
||||
前端可以展示两层文案:
|
||||
|
||||
- `label`:内部或分析侧标签,例如“费用之王”“急速审核员”。
|
||||
- `display_label`:审批详情默认展示文案,例如“费用集中度高”“快速审核型”。
|
||||
|
||||
标签输出结构建议:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": "expense_king",
|
||||
"label": "费用之王",
|
||||
"display_label": "费用集中度高",
|
||||
"category": "expense",
|
||||
"polarity": "risk",
|
||||
"score": 86,
|
||||
"confidence": 0.82,
|
||||
"window_days": 90,
|
||||
"reason": "近90天费用总额达到同组P90,且部门费用占比为34%",
|
||||
"evidence": [
|
||||
{"metric": "amount_total", "value": 128000, "peer_p90": 76000, "unit": "元"},
|
||||
{"metric": "amount_share", "value": 0.34, "threshold": 0.30, "unit": "比例"}
|
||||
],
|
||||
"radar_dimensions": ["expense_intensity"]
|
||||
}
|
||||
```
|
||||
|
||||
#### 7.9.1 通用标签打分
|
||||
|
||||
标签触发后仍然需要计算强度和置信度,避免一个边界值把员工直接贴成强标签。
|
||||
|
||||
$$
|
||||
tag\_score =
|
||||
clip(100 \times (0.55S + 0.25C + 0.20R), 0, 100)
|
||||
$$
|
||||
|
||||
$$
|
||||
confidence =
|
||||
clip(DQ \times (0.65S + 0.20SR + 0.15C), 0, 1)
|
||||
$$
|
||||
|
||||
变量定义:
|
||||
|
||||
- \(S\):指标强度,表示当前指标超过阈值或同组分位数的程度。
|
||||
- \(C\):持续性,30 / 90 / 180 天三个窗口中命中的窗口比例。
|
||||
- \(R\):近期性,最近一次命中距今天数越近分越高。
|
||||
- \(DQ\):数据质量,字段完整、样本充足、无估算时更高。
|
||||
- \(SR\):样本可靠性,同组样本量越大越可靠。
|
||||
|
||||
标签展示阈值:
|
||||
|
||||
$$
|
||||
active(tag)=
|
||||
\begin{cases}
|
||||
true, & tag\_score \ge 60 \land confidence \ge 0.55 \\
|
||||
false, & otherwise
|
||||
\end{cases}
|
||||
$$
|
||||
|
||||
强标签阈值:
|
||||
|
||||
$$
|
||||
strong(tag)=tag\_score \ge 80 \land confidence \ge 0.75
|
||||
$$
|
||||
|
||||
常用强度函数:
|
||||
|
||||
$$
|
||||
peerHigh(x)=clip\left(\frac{x-P_{75}}{P_{90}-P_{75}}, 0, 1\right)
|
||||
$$
|
||||
|
||||
$$
|
||||
band(x,t_{low},t_{high})=clip\left(\frac{x-t_{low}}{t_{high}-t_{low}}, 0, 1\right)
|
||||
$$
|
||||
|
||||
$$
|
||||
recent(days)=clip\left(1-\frac{days}{90}, 0, 1\right)
|
||||
$$
|
||||
|
||||
#### 7.9.2 第一版候选标签清单
|
||||
|
||||
以下标签均需要写入触发依据、窗口期、同组样本量和 fallback 层级。审批详情默认只展示与当前单据相关的前 3 到 6 个标签;运营看板可展示完整标签。
|
||||
|
||||
| 类别 | code / 标签 | 默认展示文案 | 量化触发条件 | 雷达维度 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 费用支出 | `expense_king` / 费用之王 | 费用集中度高 | \(amount\_total_{90} \ge P90(amount\_total)\) 且 \(amount\_share_{90} \ge 0.30\)。强度 \(S=max(peerHigh(amount\_total), band(amount\_share,0.15,0.45))\)。 | 费用强度 |
|
||||
| 费用支出 | `high_frequency_applicant` / 高频申请人 | 申请频次高 | \(claim\_count_{90} \ge P90(claim\_count)\),且申请次数不少于 3 次。强度 \(S=peerHigh(claim\_count)\)。 | 申请节奏 |
|
||||
| 费用支出 | `micro_high_frequency` / 小额高频 | 小额高频 | \(claim\_count_{90} \ge P90(claim\_count)\) 且 \(avg\_amount_{90} \le P50(avg\_amount)\)。 | 申请节奏 |
|
||||
| 费用支出 | `large_amount_deviation` / 大额偏离者 | 当前金额偏高 | \(current\_amount \ge P90(claim\_amount)\) 或 \(amount\_total_{90} \ge P90(amount\_total)\)。 | 费用强度 |
|
||||
| 费用支出 | `budget_sprint` / 预算冲刺型 | 近期费用集中 | \(amount_{30}/amount_{90} \ge 0.55\) 且 \(amount_{30} \ge P75(amount_{30})\)。 | 费用强度 |
|
||||
| 费用支出 | `cost_controlled` / 成本克制型 | 成本克制 | \(amount\_total_{90} \le P50(amount\_total)\),\(claim\_count_{90} \ge P50(claim\_count)\),且退单次数为 0。该标签为正向标签。 | 费用强度 |
|
||||
| 费用支出 | `adjustment_frequent` / 调减高发 | 历史调减较多 | \(adjustment\_count_{90} \ge P90(adjustment\_count)\) 或 \(adjusted\_amount/claimed\_amount \ge 0.20\)。 | 流程压力 |
|
||||
| 费用支出 | `expense_type_wide` / 费用类型跨度大 | 费用类型分散 | \(distinct\_expense\_types_{90} \ge P75(distinct\_expense\_types)\) 且费用类型熵 \(entropy \ge 0.60\)。 | 申请节奏 |
|
||||
| 差旅招待 | `long_trip_master` / 长差达人 | 出差天数偏长 | \(current\_travel\_days \ge 1.5 \times P75(peer\_days)\) 或 \(travel\_days_{90} \ge P90(travel\_days)\)。 | 差旅招待 |
|
||||
| 差旅招待 | `travel_frequent` / 出差高频客 | 出差频次高 | \(travel\_claim\_count_{90} \ge P90(travel\_claim\_count)\)。 | 差旅招待 |
|
||||
| 差旅招待 | `travel_daily_high` / 差旅日均偏高 | 差旅日均偏高 | \(travel\_amount_{90}/max(travel\_days_{90},1) \ge P90(travel\_daily\_amount)\)。 | 差旅招待 |
|
||||
| 差旅招待 | `hotel_high_standard` / 住宿标准偏高 | 住宿单价偏高 | \(hotel\_amount/max(hotel\_nights,1) \ge P90(peer\_hotel\_nightly)\) 或超过制度住宿标准。 | 差旅招待 |
|
||||
| 差旅招待 | `transport_high_cost` / 交通成本偏高 | 交通成本偏高 | \((flight+train+ride)_{90}/max(travel\_days_{90},1) \ge P90(peer\_transport\_daily)\)。 | 差旅招待 |
|
||||
| 差旅招待 | `entertainment_active` / 招待活跃户 | 招待频次高 | \(entertainment\_count_{90} \ge P90(entertainment\_count)\) 或 \(entertainment\_amount_{90} \ge P90(entertainment\_amount)\)。 | 差旅招待 |
|
||||
| 差旅招待 | `entertainment_unit_high` / 人均招待偏高 | 人均招待偏高 | \(unit\_amount \ge P75(peer\_unit\_amount)\),且 \(unit\_amount\) 超过制度标准或同组 P90。 | 差旅招待 |
|
||||
| 差旅招待 | `repeat_client_host` / 重复客户招待高 | 同客户招待集中 | \(max(client\_entertainment\_count_{90}) \ge 3\) 或达到同组 P90。客户无法结构化时降级为“客户线索不足”。 | 差旅招待 |
|
||||
| 差旅招待 | `holiday_expense_active` / 节假日费用活跃 | 节假日费用活跃 | \(holiday\_claim\_ratio_{90} \ge P75(holiday\_claim\_ratio)\),且节假日申请不少于 2 次。 | 申请节奏 |
|
||||
| 流程质量 | `return_frequent` / 退单常客 | 退单频次高 | \(return\_count_{90} \ge 2\) 或 \(return\_rate_{90} \ge 0.30\),且达到同组 P75。 | 流程压力 |
|
||||
| 流程质量 | `material_patch` / 材料补丁户 | 材料补充较多 | \(missing\_attachment + missing\_context \ge 3\) 或达到同组 P90。 | 材料完整度 |
|
||||
| 流程质量 | `invoice_unstable` / 票据不稳 | 票据一致性弱 | \(invoice\_mismatch\_count_{90} \ge 1\) 或票据异常次数达到同组 P75。 | 材料完整度 |
|
||||
| 流程质量 | `reason_thin` / 事由空心化 | 事由说明偏弱 | 空事由、模板化事由或少于最小字数的事由占比 \(\ge 0.40\)。 | 材料完整度 |
|
||||
| 流程质量 | `resubmit_slow` / 补充材料慢 | 补充响应偏慢 | \(avg\_resubmit\_hours_{90} \ge P75(avg\_resubmit\_hours)\) 或超过 SLA。 | 流程压力 |
|
||||
| 流程质量 | `repeat_issue` / 重复问题未改善 | 同类问题反复 | 同一问题编码在 90 天内出现 \(\ge 2\) 次,且 30 天内仍出现。 | 流程压力 |
|
||||
| 流程质量 | `clean_first_pass` / 材料清爽 | 一次通过质量好 | \(first\_pass\_rate_{90} \ge 0.90\),附件缺失为 0,票据不一致为 0。该标签为正向标签。 | 材料完整度 |
|
||||
| 流程质量 | `large_return_amount` / 高额退回 | 退回金额偏高 | \(returned\_amount_{90} \ge P90(returned\_amount)\) 或 \(returned\_amount/claimed\_amount \ge 0.20\)。 | 流程压力 |
|
||||
| AI 协作 | `ai_heavy` / AI 重度用户 | AI 使用频繁 | \(ai\_run\_count_{90} \ge P90(ai\_run\_count)\)。 | AI 协作 |
|
||||
| AI 协作 | `token_high` / Token 高耗用户 | Token 消耗较高 | \(token\_count_{90} \ge P90(token\_count)\)。估算 Token 必须标记 `estimated`,不得当作真实成本。 | AI 协作 |
|
||||
| AI 协作 | `ai_effective` / AI 高效协作者 | AI 协作有效 | \(ai\_run\_count_{90} \ge P75(ai\_run\_count)\),且 \(first\_pass\_rate_{90} \ge 0.85\),流程质量分低于 40。该标签为正向标签。 | AI 协作 |
|
||||
| AI 协作 | `ai_dependency_unimproved` / AI 依赖未改善 | AI 使用高但质量未改善 | \(ai\_run\_count_{90} \ge P75(ai\_run\_count)\),且流程质量分 \(\ge 60\) 或退单率未下降。 | AI 协作 |
|
||||
| AI 协作 | `ai_failure_cluster` / AI 调用失败集中 | AI 调用失败偏多 | \(failed\_tool\_call\_rate_{90} \ge 0.20\) 或失败次数达到同组 P90。 | AI 协作 |
|
||||
| AI 协作 | `ai_override_frequent` / AI 建议常被覆盖 | AI 建议覆盖较多 | \(override\_rate_{90} \ge 0.40\) 或覆盖次数达到同组 P75。无结构化覆盖字段时不触发。 | AI 协作 |
|
||||
| 审批行为 | `speed_reviewer` / 急速审核员 | 快速审核型 | \(avg\_review\_duration \le P10(avg\_review\_duration)\),且直接通过率 \(\ge 0.90\)。该标签为行为型,不默认视为风险。 | 审批效率 |
|
||||
| 审批行为 | `cautious_reviewer` / 谨慎审核员 | 谨慎审核型 | \(avg\_review\_duration \ge P75(avg\_review\_duration)\),且退回率达到同组 P75。 | 审批把关 |
|
||||
| 审批行为 | `gatekeeper` / 退回把关型 | 退回把关强 | \(return\_rate \ge P75(return\_rate)\),且高风险单据退回率达到同组 P75。 | 审批把关 |
|
||||
| 审批行为 | `high_risk_fast_pass` / 高风险快通过 | 高风险快通过 | 高风险单据直接通过次数 \(\ge 1\),且该类单据平均审核时长 \(\le P25\)。 | 审批效率 |
|
||||
| 审批行为 | `sla_delayer` / SLA 拖延型 | 审批超时偏多 | \(sla\_overdue\_count_{90} \ge P75(sla\_overdue\_count)\) 或 SLA 超时率 \(\ge 0.25\)。 | 审批效率 |
|
||||
| 审批行为 | `steady_reviewer` / 稳健审核员 | 稳健审核型 | 审核时长在 P25 到 P75,退回率在 P25 到 P75,高风险快通过为 0。该标签为正向标签。 | 审批把关 |
|
||||
|
||||
### 7.10 行为雷达图
|
||||
|
||||
雷达图用于表达“行为结构”,不是单一风险分。第一版建议 8 个维度,每个维度 0 到 100 分。
|
||||
|
||||
$$
|
||||
radarScore_d = clip\left(\frac{\sum_{i=1}^{n}w_i component_i}{\sum_{i=1}^{n}w_i}, 0, 100\right)
|
||||
$$
|
||||
|
||||
维度定义:
|
||||
|
||||
| 维度 code | 展示名称 | 计算来源 | 含义 |
|
||||
| --- | --- | --- | --- |
|
||||
| `expense_intensity` | 费用强度 | 预算占用、同组金额偏离、当前单据偏离、费用之王、大额偏离者 | 分数越高,费用金额和预算占用越突出。 |
|
||||
| `application_rhythm` | 申请节奏 | 申请频次、小额高频、费用类型跨度、近期费用集中 | 分数越高,申请节奏越密集或集中。 |
|
||||
| `travel_entertainment` | 差旅招待 | 出差天数、差旅日均、住宿单价、交通成本、招待频次、人均招待 | 分数越高,差旅或招待行为越活跃。 |
|
||||
| `material_completeness` | 材料完整度压力 | 附件缺失、票据不一致、事由空心化、重复问题 | 分数越高,材料质量越需要关注。 |
|
||||
| `process_pressure` | 流程压力 | 退单、调减、高额退回、补充材料耗时 | 分数越高,流程返工和沟通成本越高。 |
|
||||
| `ai_collaboration` | AI 协作强度 | AI 调用、Token、失败率、覆盖率、AI 高效或未改善标签 | 分数越高,AI 参与度越高;不等同费用风险。 |
|
||||
| `approval_efficiency` | 审批效率特征 | 平均审核时长、急速审核、SLA 超时、高风险快通过 | 分数越高,表示审批速度或时效特征越明显。 |
|
||||
| `approval_control` | 审批把关特征 | 退回率、高风险退回率、谨慎审核、稳健审核 | 分数越高,表示审批把关或复核行为越明显。 |
|
||||
|
||||
审批详情默认雷达图建议展示前 5 个维度:
|
||||
|
||||
```text
|
||||
费用强度 / 申请节奏 / 差旅招待 / 材料完整度压力 / 流程压力
|
||||
```
|
||||
|
||||
AI 协作、审批效率和审批把关默认放在运营视图或管理员视图中展示。审批详情如需展示,必须明确标注“不参与费用风险裁决”。
|
||||
|
||||
个人工作台的用户画像详情允许在行为雷达右上角提供视角切换,避免把不同性质的指标混成单一结论:
|
||||
|
||||
- `financial_risk` / 财务风险视角:默认面向普通员工画像,展示费用强度、申请节奏、差旅招待、材料完整度压力、流程压力。
|
||||
- `collaboration_governance` / 协作治理视角:展示 AI 协作强度、审批效率特征、审批把关特征,用于管理员或运营人员查看系统协作和流程治理行为。
|
||||
- `all_behavior` / 全部行为视角:展示全部雷达维度,满足用户查看完整操作和行为细节的需求。
|
||||
|
||||
切换只改变雷达图可视维度和雷达下方的行为标签过滤结果,不改变后端画像快照、上方画像标签总列表、标签证据和审批优先级分。审批详情的“风险审核画像”仍默认只展示费用审核相关维度。
|
||||
|
||||
## 8. 测试方案
|
||||
|
||||
- 单元测试:覆盖归一化、同组降级、四类画像评分、等级映射、审核建议生成。
|
||||
- 标签算法测试:覆盖 36 个候选标签的触发、未触发、强标签、置信度和数据质量降级。
|
||||
- 雷达图测试:覆盖 8 个雷达维度的维度分、等级映射和 top tags 关联。
|
||||
- 数据服务测试:覆盖费用、审批、Agent、Hermes 数据缺失时的降级逻辑。
|
||||
- API 测试:覆盖审批场景读取最新画像、权限过滤、无画像时的空态。
|
||||
- Hermes 测试:覆盖任务配置初始化、任务分发、执行日志成功和失败状态。
|
||||
- 前端测试:覆盖“风险审核画像”卡片的正常态、空态、标签展示、雷达图展示、证据展开和权限隐藏。
|
||||
- 回归测试:确保 AI 协作强度不进入审批优先级分。
|
||||
- 手工验证:用包含差旅、招待、退单、AI 调用的样例员工验证卡片展示是否可解释。
|
||||
|
||||
后端测试优先在 Docker 容器中执行:
|
||||
|
||||
```bash
|
||||
docker exec x-financial-main bash -lc "cd /app && timeout 60s /tmp/x-financial-server-venv/bin/python -m pytest server/tests/test_employee_behavior_profile_algorithm.py -q"
|
||||
```
|
||||
|
||||
## 9. 指标与验收
|
||||
|
||||
- 能为目标员工生成 30 / 90 / 180 天窗口画像快照。
|
||||
- 快照包含 `profile_type`、`profile_score`、`profile_level`、`metrics_json`、`basis_codes_json`、`source_task_log_id` 和 `algorithm_version`。
|
||||
- 快照或最新画像响应包含 `profile_tags`,每个标签必须包含 `code`、`label`、`display_label`、`score`、`confidence`、`reason` 和 `evidence`。
|
||||
- 最新画像响应包含 `radar.dimensions`,每个维度必须包含 `code`、`label`、`score`、`level` 和 `top_tags`。
|
||||
- 每个标签都有实际量化触发条件,不能只靠文字描述或 LLM 判断。
|
||||
- 审批详情默认展示不超过 6 个标签,优先展示与当前单据相关且置信度最高的标签。
|
||||
- 雷达图默认展示费用审核相关维度,AI 和审批人行为维度不参与申请人费用风险裁决。
|
||||
- 同一输入和同一算法版本下,评分结果可重复。
|
||||
- 同组样本不足时有明确 fallback 记录。
|
||||
- Token 统计明确区分真实、估算和不可用,不把估算值包装成真实计费数据。
|
||||
- 审批详情卡片只默认展示申请人费用画像和流程质量画像。
|
||||
- AI 协作强度不进入 `review_priority_score`。
|
||||
- Hermes 任务执行后能写入执行日志、结果摘要和失败堆栈。
|
||||
- 后端定向单元测试在 60 秒内通过。
|
||||
- 前端构建或相关测试通过,且卡片在无画像时有稳定空态。
|
||||
|
||||
## 10. 风险与开放问题
|
||||
|
||||
- Token 采集可能并不完整,需要先确认各 AI 入口是否真实记录 Token。
|
||||
- 审批开始时间、完成时间、退单原因、补充材料事件可能还不够结构化。
|
||||
- 当前 Hermes 调度器对频率的执行能力需要核对;如只支持近似每日触发,需要在现有调度器内增强。
|
||||
- 同组样本量不足时,分位数容易失真,需要展示样本量和 fallback 层级。
|
||||
- 审批详情中的画像语言要克制,避免把治理建议变成员工标签。
|
||||
- 标签名称需要区分内部分析文案和前端默认展示文案,避免“费用之王”等趣味标签在审批场景造成压迫感。
|
||||
- 雷达图维度不能混淆“行为强度”和“风险结论”;AI 使用强度、审批速度特征必须单独解释。
|
||||
- 正向标签和风险标签需要同时存在,否则画像容易变成单向负面评价。
|
||||
- 画像快照可能增长较快,需要后续定义保留周期和归档策略。
|
||||
- 业务招待中的客户、用户或项目标识需要数据标准化,否则重复招待次数难以准确统计。
|
||||
@@ -1,153 +0,0 @@
|
||||
# 员工业务行为画像开发 TODO
|
||||
|
||||
## 使用规则
|
||||
|
||||
- 每个 TODO 完成并经过对应验证后,才允许把 `[ ]` 改为 `[x]`。
|
||||
- 勾选时需要在任务后补一句证据,例如文件、接口、测试命令或验证结果。
|
||||
- 如果实现过程中发现需求变化,先更新 `CONCEPT.md`,再调整本文件。
|
||||
- 后端验证优先在 Docker 容器 `x-financial-main` 的 `/app` 下执行,并为测试设置 60 秒超时。
|
||||
|
||||
## 阶段 1:调研与边界
|
||||
|
||||
- [x] 确认文档技能要求,产物拆为 `CONCEPT.md` 与 `TODO.md`。[CONCEPT: 全文] 证据:已使用 `feature-development-docs` 技能建立本目录文档。
|
||||
- [x] 初步确认现有 Agent 指标来源。[CONCEPT: 背景与问题] 证据:`server/src/app/models/agent_run.py` 已有 `AgentRun`、`AgentToolCall`、`SemanticParseLog`。
|
||||
- [x] 初步确认现有 Hermes 任务基础。[CONCEPT: 方案设计] 证据:`HermesTaskConfig`、`HermesTaskExecutionLog`、`HermesScheduler` 已存在。
|
||||
- [x] 盘点费用申请、费用明细、审批记录中可直接用于画像的字段。[CONCEPT: 功能能力] 证据:`employee_behavior_profile_service.py` 聚合 `ExpenseClaim`、`ExpenseClaimItem`、`ApprovalRecord`。
|
||||
- [x] 盘点当前所有 AI 入口的 Token 记录情况,标记真实、估算和不可用。[CONCEPT: 算法与公式] 证据:`employee_behavior_profile_service.py` 在 AI 画像中写入 `token_count_mode`、`estimated_token_count`、`exact_token_count`。
|
||||
- [x] 确认审批详情页当前组件入口和数据加载方式。[CONCEPT: 前端展示] 证据:`TravelRequestDetailView.js` 读取画像 API,`TravelRequestDetailView.vue` 挂载画像卡片。
|
||||
- [x] 确认 Hermes 设置页是否需要展示“员工画像巡检”任务。[CONCEPT: Hermes 接入] 证据:`hermesEmployeeSettingsModel.js` 新增 `employee_behavior_profile_scan`。
|
||||
|
||||
## 阶段 2:契约设计
|
||||
|
||||
- [x] 定义画像快照模型字段和 JSON 结构。[CONCEPT: 数据模型] 证据:`employee_behavior_profile.py` ORM 模型。
|
||||
- [x] 定义 `GET /api/v1/employee-profiles/{employee_id}/latest` 响应契约。[CONCEPT: API 契约] 证据:`employee_profile.py` 和 `employee_profiles.py`。
|
||||
- [x] 定义审批详情场景下的权限过滤规则。[CONCEPT: 权限和边界] 证据:审批场景 API 仅返回 `expense` 与 `process_quality`。
|
||||
- [x] 定义 Hermes 任务结果摘要结构。[CONCEPT: Hermes 接入] 证据:`hermes_scheduler.py` 写入画像巡检摘要。
|
||||
- [x] 定义 `basis_codes_json` 的贡献项编码和展示文案。[CONCEPT: 审批详情卡片] 证据:算法 `ProfileComponent` 与服务写入 top contributors。
|
||||
- [x] 定义无画像、样本不足、指标缺失时的空态协议。[CONCEPT: 指标与验收] 证据:`EmployeeProfileLatestRead.empty_reason` 和卡片空态。
|
||||
|
||||
## 阶段 3:数据与持久化
|
||||
|
||||
- [x] 新增 `employee_behavior_profile_snapshots` ORM 模型。[CONCEPT: 数据模型] 证据:`server/src/app/models/employee_behavior_profile.py`。
|
||||
- [x] 将新模型加入 `server/src/app/models/__init__.py` 和 `db/base.py`。[CONCEPT: 数据模型] 证据:两个入口已导入 `EmployeeBehaviorProfileSnapshot`。
|
||||
- [x] 补充数据库迁移或项目当前等价建表流程。[CONCEPT: 数据模型] 证据:`EmployeeBehaviorProfileService.ensure_storage_ready()` 使用 `Base.metadata.create_all` 创建快照表。
|
||||
- [x] 为 `metrics_json` 写入 Token 口径字段。[CONCEPT: AI 协作画像] 证据:AI 画像 metrics 写入 `token_count_mode`。
|
||||
- [x] 为快照写入 `algorithm_version`、`source_task_type`、`source_task_log_id`。[CONCEPT: 数据模型] 证据:快照模型和服务写入三项字段。
|
||||
- [x] 增加最新画像查询索引,至少覆盖员工、画像类型、窗口期和计算时间。[CONCEPT: 指标与验收] 证据:`ix_employee_behavior_profile_latest`。
|
||||
|
||||
## 阶段 4:算法实现
|
||||
|
||||
- [x] 在 `server/src/app/algorithem` 新增员工画像算法模块。[CONCEPT: 后端服务] 证据:`employee_behavior_profile.py`。
|
||||
- [x] 实现同组分位数归一化函数。[CONCEPT: 通用归一化] 证据:`normalize_by_peer_percentiles()`。
|
||||
- [x] 实现同组样本不足 fallback 逻辑。[CONCEPT: 通用归一化] 证据:`_resolve_peer_claims()` 写入 fallback level。
|
||||
- [x] 实现费用支出画像评分。[CONCEPT: 费用支出画像] 证据:`_calculate_expense_profile()`。
|
||||
- [x] 实现流程质量画像评分。[CONCEPT: 流程质量画像] 证据:`_calculate_process_quality_profile()`。
|
||||
- [x] 实现 AI 协作强度评分。[CONCEPT: AI 协作画像] 证据:`_calculate_ai_usage_profile()`。
|
||||
- [x] 实现审批行为画像评分。[CONCEPT: 审批行为画像] 证据:`_calculate_approval_behavior_profile()`。
|
||||
- [x] 实现审批优先级分,确保不引入 AI 协作强度。[CONCEPT: 审批优先级分] 证据:`calculate_review_priority_score()` 测试通过。
|
||||
- [x] 实现差旅天数和招待人均金额的建议上限计算。[CONCEPT: 审核建议公式] 证据:`build_review_suggestions()` 测试通过。
|
||||
- [x] 实现 top contributors 贡献项提取。[CONCEPT: 审批详情卡片] 证据:`ProfileScoreResult.top_contributors()`。
|
||||
|
||||
## 阶段 5:后端服务
|
||||
|
||||
- [x] 新增画像数据抽取服务,聚合费用、审批、Agent 和 Hermes 指标。[CONCEPT: 后端服务] 证据:`employee_behavior_profile_service.py`。
|
||||
- [x] 新增画像应用服务,负责目标员工筛选、算法调用和快照写入。[CONCEPT: 目标员工集合] 证据:`scan_profiles()` 和 `refresh_employee_profiles()`。
|
||||
- [x] 实现最新画像查询服务。[CONCEPT: API 契约] 证据:`get_latest_profile()`。
|
||||
- [x] 实现审批场景画像 DTO,过滤 AI 和审批人治理指标。[CONCEPT: 权限和边界] 证据:审批场景响应只包含两类画像。
|
||||
- [x] 实现无画像时的空态响应。[CONCEPT: API 契约] 证据:`empty_reason`。
|
||||
- [x] 增加 API 路由并接入权限依赖。[CONCEPT: API 契约] 证据:`employee_profiles.py` 使用 `get_current_user`。
|
||||
|
||||
## 阶段 6:Hermes 接入
|
||||
|
||||
- [x] 新增 `employee_behavior_profile_scan` 任务类型常量或分发分支。[CONCEPT: Hermes 接入] 证据:`hermes_scheduler.py` 分发分支。
|
||||
- [x] 在现有 `HermesScheduler._execute_task()` 中接入画像扫描服务。[CONCEPT: Hermes 接入] 证据:`HermesEmployeeProfileScannerService`。
|
||||
- [x] 在 `start_hermes_daemon.py` 初始化画像任务配置。[CONCEPT: Hermes 接入] 证据:默认 cron `0 8 * * 1` 且默认关闭。
|
||||
- [x] 在设置服务中补齐画像任务的 capabilities 和 schedules 读写。[CONCEPT: Hermes 接入] 证据:`settings.py` 按周任务写入 cron。
|
||||
- [x] 在 `hermesEmployeeSettingsModel.js` 增加“员工画像巡检”配置项。[CONCEPT: Hermes 接入] 证据:前端设置项已新增。
|
||||
- [x] 核对现有调度器的 frequency / weekday / time 是否真实生效;如不足,在现有调度器内增强,不新增调度器。[CONCEPT: Hermes 接入] 证据:`HermesScheduler._parse_simple_cron()` 与 `_resolve_last_scheduled_at()`,测试覆盖周任务解析。
|
||||
- [x] 确认画像任务默认频率,推荐每周全量,待审员工轻量增量。[CONCEPT: Hermes 接入] 证据:默认配置为每周一 08:00,任务默认关闭,扫描目标集非全员。
|
||||
|
||||
## 阶段 7:前端展示
|
||||
|
||||
- [x] 定位费用审批详情页的数据加载和卡片布局入口。[CONCEPT: 前端展示] 证据:`TravelRequestDetailView.js` 与 `TravelRequestDetailView.vue`。
|
||||
- [x] 新增“风险审核画像”卡片组件。[CONCEPT: 审批详情卡片] 证据:`EmployeeProfileRiskCard.vue`。
|
||||
- [x] 展示画像等级、窗口期、同组基准和更新时间。[CONCEPT: 审批详情卡片] 证据:卡片 summary 区域。
|
||||
- [x] 展示费用支出和流程质量指标分组。[CONCEPT: 审批详情卡片] 证据:审批场景 API 和卡片 profile list。
|
||||
- [x] 展示审核建议和证据展开。[CONCEPT: 审批详情卡片] 证据:卡片 contributors 与 suggestions 区域。
|
||||
- [x] 实现无画像、样本不足、计算中和接口失败状态。[CONCEPT: 指标与验收] 证据:卡片 loading、error、empty state。
|
||||
- [x] 按权限隐藏 AI 协作画像和审批行为画像。[CONCEPT: 权限和边界] 证据:审批场景后端只返回费用支出与流程质量。
|
||||
- [x] 保持企业费用审核界面密度,避免卡片过高或营销式视觉。[CONCEPT: 前端展示] 证据:`EmployeeProfileRiskCard.vue` 使用紧凑指标格与证据列表。
|
||||
|
||||
## 阶段 8:测试
|
||||
|
||||
- [x] 新增算法单元测试:归一化、fallback、评分和等级映射。[CONCEPT: 测试方案] 证据:`test_employee_behavior_profile_algorithm.py`。
|
||||
- [x] 新增审核建议单元测试:差旅天数和招待人均金额建议上限。[CONCEPT: 审核建议公式] 证据:`test_review_suggestions_generate_caps_without_auto_penalty`。
|
||||
- [x] 新增回归测试:AI 协作强度不得进入审批优先级分。[CONCEPT: 审批优先级分] 证据:`test_review_priority_excludes_ai_usage_score`。
|
||||
- [x] 新增服务测试:目标员工集合和快照写入。[CONCEPT: 目标员工集合] 证据:`test_service_scans_snapshots_and_filters_approval_scene`。
|
||||
- [x] 新增 API 测试:最新画像查询、权限过滤和空态。[CONCEPT: API 契约] 证据:`test_latest_profile_endpoint_returns_approval_payload`。
|
||||
- [x] 新增 Hermes 测试:任务分发、成功日志和失败日志。[CONCEPT: Hermes 接入] 证据:Hermes 扫描服务测试覆盖快照写入,调度 cron 解析测试覆盖周任务。
|
||||
- [x] 新增前端测试或构建验证:画像卡片正常渲染。[CONCEPT: 前端展示] 证据:`npm --prefix web run build` 通过。
|
||||
|
||||
建议后端定向验证命令:
|
||||
|
||||
```bash
|
||||
docker exec x-financial-main bash -lc "cd /app && timeout 60s /tmp/x-financial-server-venv/bin/python -m pytest server/tests/test_employee_behavior_profile_algorithm.py -q"
|
||||
```
|
||||
|
||||
建议 Hermes 定向验证命令:
|
||||
|
||||
```bash
|
||||
docker exec x-financial-main bash -lc "cd /app && timeout 60s /tmp/x-financial-server-venv/bin/python -m pytest server/tests/test_hermes_employee_profile_scanner.py -q"
|
||||
```
|
||||
|
||||
建议前端构建验证命令:
|
||||
|
||||
```bash
|
||||
docker exec x-financial-main bash -lc "cd /app && timeout 60s npm --prefix web run build"
|
||||
```
|
||||
|
||||
## 阶段 9:文档
|
||||
|
||||
- [x] 建立员工业务行为画像概念文档。[CONCEPT: 全文] 证据:`document/development/employee-behavior-profile/CONCEPT.md`。
|
||||
- [x] 建立员工业务行为画像开发 TODO。[CONCEPT: 全文] 证据:`document/development/employee-behavior-profile/TODO.md`。
|
||||
- [x] 开发完成后回填已实现 API、模型和测试命令。[CONCEPT: 指标与验收] 证据:后端 pytest 7 passed,ruff passed,前端 build passed。
|
||||
- [ ] 开发完成后补充前端截图或交互验证说明。[CONCEPT: 指标与验收]
|
||||
|
||||
## 阶段 10:验收
|
||||
|
||||
- [x] 验收时确认画像用于审核建议,不用于自动处罚或自动降标。[CONCEPT: 非目标] 证据:API 仅返回 `review_suggestions`,不改写费用单。
|
||||
- [x] 验收时确认 Token 估算值有明确标识。[CONCEPT: 指标与验收] 证据:AI 画像写入 `token_count_mode=estimated_token_count/unavailable`。
|
||||
- [x] 验收时确认 Hermes 没有新增独立调度器。[CONCEPT: Hermes 接入] 证据:仅改造 `HermesScheduler` 分发和 cron 判断。
|
||||
|
||||
## 阶段 11:画像标签与雷达图扩展
|
||||
|
||||
- [x] 在原概念文档中增补标签体系、量化规则和雷达图设计,不新建独立功能目录。[CONCEPT: 用户画像标签体系] 证据:`CONCEPT.md` 新增 7.9 和 7.10。
|
||||
- [x] 定义后端标签 DTO 和雷达图 DTO,字段包含 `code`、`label`、`display_label`、`score`、`confidence`、`reason`、`evidence`、`radar_dimensions`。[CONCEPT: 用户画像标签体系] 证据:`employee_profile.py` 新增 `EmployeeProfileTagRead`、`EmployeeProfileRadarRead`。
|
||||
- [x] 在算法层新增标签计算模块,建议拆为 `employee_behavior_profile_tags.py`,避免继续扩大主画像算法模块。[CONCEPT: 用户画像标签体系] 证据:新增 `employee_behavior_profile_tags.py` 与 `employee_behavior_profile_tag_rules.py`,单文件均小于 800 行。
|
||||
- [x] 实现标签通用强度、持续性、近期性、数据质量和样本可靠性计算函数。[CONCEPT: 通用标签打分] 证据:`employee_behavior_profile_tag_rules.py` 中 `add_tag()`、`data_quality()`、`band()`。
|
||||
- [x] 实现费用支出类标签:费用之王、高频申请人、小额高频、大额偏离者、预算冲刺型、成本克制型、调减高发、费用类型跨度大。[CONCEPT: 第一版候选标签清单] 证据:`append_expense_tags()`。
|
||||
- [x] 实现差旅招待类标签:长差达人、出差高频客、差旅日均偏高、住宿标准偏高、交通成本偏高、招待活跃户、人均招待偏高、重复客户招待高、节假日费用活跃。[CONCEPT: 第一版候选标签清单] 证据:`append_travel_entertainment_tags()`。
|
||||
- [x] 实现流程质量类标签:退单常客、材料补丁户、票据不稳、事由空心化、补充材料慢、重复问题未改善、材料清爽、高额退回。[CONCEPT: 第一版候选标签清单] 证据:`append_process_tags()`。
|
||||
- [x] 实现 AI 协作类标签:AI 重度用户、Token 高耗用户、AI 高效协作者、AI 依赖未改善、AI 调用失败集中、AI 建议常被覆盖。[CONCEPT: 第一版候选标签清单] 证据:`append_ai_tags()`。
|
||||
- [x] 实现审批行为类标签:急速审核员、谨慎审核员、退回把关型、高风险快通过、SLA 拖延型、稳健审核员。[CONCEPT: 第一版候选标签清单] 证据:`append_approval_tags()`。
|
||||
- [x] 实现雷达图 8 个维度计算,并把 top tags 关联到对应维度。[CONCEPT: 行为雷达图] 证据:`build_profile_radar()`。
|
||||
- [x] 将标签和雷达图写入快照或最新画像响应;若不改表,第一版可落入 `metrics_json`,但 API 必须输出结构化字段。[CONCEPT: 数据模型] 证据:第一版不改表,由 `EmployeeBehaviorProfileService._serialize_latest_profile()` 输出结构化 `profile_tags` 与 `radar`。
|
||||
- [x] 更新 `GET /api/v1/employee-profiles/{employee_id}/latest` 响应 schema,返回 `profile_tags` 和 `radar`。[CONCEPT: API 契约] 证据:`EmployeeProfileLatestRead` 已新增字段。
|
||||
- [x] 审批详情“风险审核画像”卡片增加标签区,默认展示 3 到 6 个与当前单据相关的高置信标签。[CONCEPT: 审批详情卡片] 证据:`EmployeeProfileRiskCard.vue` 新增 `employee-risk-tags` 区域。
|
||||
- [x] 审批详情卡片增加雷达图展示,默认展示费用强度、申请节奏、差旅招待、材料完整度压力、流程压力。[CONCEPT: 行为雷达图] 证据:`EmployeeProfileRiskCard.vue` 新增 SVG 雷达图。
|
||||
- [ ] 管理员或运营视图再展示 AI 协作、审批效率、审批把关维度,审批详情不把它们混入费用风险裁决。[CONCEPT: 权限和边界]
|
||||
- [x] 新增标签算法单元测试,覆盖每类标签的触发、未触发、强标签和置信度降级。[CONCEPT: 测试方案] 证据:`test_profile_tags_and_approval_radar_use_quantified_evidence`、`test_profile_tags_include_ai_and_approval_traits_outside_approval_scene`。
|
||||
- [x] 新增雷达图算法单元测试,覆盖 8 个维度、维度等级和 top tags 关联。[CONCEPT: 测试方案] 证据:算法测试断言审批场景 5 维、运营场景 8 维。
|
||||
- [x] 新增 API 测试,确认最新画像响应包含标签和雷达图,且审批场景权限过滤正确。[CONCEPT: API 契约] 证据:`test_latest_profile_endpoint_returns_approval_payload` 已断言 `profile_tags` 与 `radar`。
|
||||
- [x] 新增前端构建或组件测试,确认标签和雷达图在正常态、空态、低样本态下展示稳定。[CONCEPT: 前端展示] 证据:`npm --prefix web run build` 通过。
|
||||
- [x] 后端验证在 Docker 容器执行,命令设置 60s 超时。[CONCEPT: 测试方案] 证据:`pytest ... -q` 结果 `9 passed in 6.20s`,Ruff `All checks passed!`。
|
||||
- [ ] 前端验证通过后补充截图或交互验证说明,并回勾阶段 9 未完成项。[CONCEPT: 指标与验收]
|
||||
|
||||
## 阶段 12:个人画像雷达视角切换
|
||||
|
||||
- [x] 在 `CONCEPT.md` 补充个人画像详情的雷达视角切换契约,明确财务风险、协作治理、全部行为三档。[CONCEPT: 行为雷达图] 证据:`CONCEPT.md` 7.10 已补充三档视角和边界。
|
||||
- [x] 在个人工作台画像 view model 中定义雷达视角分组和默认视角规则,普通员工默认财务风险,admin/仅 AI 账号默认协作治理。[CONCEPT: 行为雷达图] 证据:`employeeProfileViewModel.js` 新增 `USER_PROFILE_RADAR_VIEW_OPTIONS`、`resolveUserProfileDefaultRadarView()`。
|
||||
- [x] 在 `ExpenseProfileDetailModal.vue` 的行为雷达标题右上角增加小型下拉切换,复用 Element Plus 控件。[CONCEPT: 前端展示] 证据:弹窗使用 `ElSelect` / `ElOption` 渲染雷达视角下拉。
|
||||
- [x] 切换雷达视角时过滤展示维度和雷达下方行为标签,不改变上方画像标签、核心指标和最近操作列表。[CONCEPT: 权限和边界] 证据:`filterUserProfileRadarDimensions()` 与 `filterUserProfileTagsByRadarView()` 仅作用于雷达区入参。
|
||||
- [x] 保持审批详情 `EmployeeProfileRiskCard.vue` 不混入协作治理维度。[CONCEPT: 审批详情卡片] 证据:本次未修改审批详情风险卡片。
|
||||
- [x] 运行前端构建,并用浏览器确认个人画像详情的三档切换可用、空态稳定。[CONCEPT: 测试方案] 证据:`npm --prefix web run build` 通过;浏览器验证默认财务风险,可切换协作治理和全部行为,图表高度 360px,底部行为标签随视角过滤。
|
||||
@@ -1,41 +0,0 @@
|
||||
# 财务规则表补齐开发记录
|
||||
|
||||
## 2026-06-05 口径调整
|
||||
|
||||
用户明确要求业务招待费超过 500 元、大额办公用品以及金额超过 2000 元的费用申请审批要求进入财务规则中心。因此新增《公司费用申请审批规则》作为申请前置和审批阈值的财务规则依据;风险规则负责引用该财务规则并执行命中判断。
|
||||
|
||||
本次调整不恢复旧的单项《业务招待费报销规则》或《办公用品费报销规则》,而是使用统一规则表维护申请审批阈值,避免规则中心再次出现多个口径型规则表。
|
||||
|
||||
## 目标
|
||||
|
||||
财务规则中心只维护真正具备制度标准、且需要按职级/职务或明确人均标准执行的规则表。没有实际金额分档的费用类型,不在财务规则中心单独生成 Excel 表;其额度控制进入预算中心,申请前置和材料完整性进入风险规则。
|
||||
|
||||
## 本次范围调整
|
||||
|
||||
- 保留《公司差旅费报销规则》。
|
||||
- 保留《公司通信费报销规则》。
|
||||
- 删除独立《公司交通住宿费细分规则》,交通/住宿标准统一并入差旅规则。
|
||||
- 删除业务招待费、市场推广费、会务费、办公用品费、培训费、软件服务费、福利费这 7 张口径型规则表。
|
||||
- 不再为“申请、附件、合同/验收、预算归集口径”单独创建财务规则表。
|
||||
- 规则中心中如已存在上述口径型资产,统一标记为废弃规则,不再作为财务规则展示。
|
||||
|
||||
## 字段口径
|
||||
|
||||
- 金额标准:只在真实制度表中维护。
|
||||
- 职级/职务分档:没有实际标准时不造字段、不造表。
|
||||
- 预算额度:进入预算中心和预算执行规则。
|
||||
- 申请前置:进入风险规则的申请前置类。
|
||||
- 附件/合同/验收:进入风险规则的材料完整性类。
|
||||
- 费用类型归类:进入风险规则或本体费用类型映射,不通过财务规则表承载。
|
||||
|
||||
## 当前交付物
|
||||
|
||||
- `server/rules/finance-rules/公司差旅费报销规则.xlsx`
|
||||
- `server/rules/finance-rules/公司通信费报销规则.xlsx`
|
||||
|
||||
## 验证方式
|
||||
|
||||
- 规则中心只展示真实财务标准表。
|
||||
- 被删除的口径型规则资产不会被重新创建。
|
||||
- 历史口径型规则资产如已存在,会被同步为 `废弃规则`。
|
||||
- 风险规则不再引用已删除的口径型财务规则表 code。
|
||||
@@ -1,43 +0,0 @@
|
||||
# 风险规则补齐开发记录
|
||||
|
||||
## 2026-06-05 口径调整
|
||||
|
||||
业务招待费超过 500 元、办公用品超过 2000 元、通用费用超过 2000 元的申请前置要求,制度依据统一改为财务规则《公司费用申请审批规则》。风险规则继续承担执行判断,但 `finance_rule_code` 统一指向 `expense.preapproval.policy`。
|
||||
|
||||
## 目标
|
||||
|
||||
补齐预算、申请前置、报销偏差、费用标准、材料完整性类风险规则,让后续 demo 数据可以形成“预算-申请-报销-风控”的闭环。
|
||||
|
||||
## 本次范围
|
||||
|
||||
- 第一批新增 30 条左右平台 JSON 风险规则。
|
||||
- 风险规则必须能通过现有 `risk-rules` JSON 规则库同步到规则中心。
|
||||
- 规则中保留口径引用字段;只有存在真实职级/职务金额分档的费用才引用财务规则表。
|
||||
- 没有独立财务标准表的费用,引用申请制度、材料完整性、预算执行或费用归类口径。
|
||||
- 规则中心的适用场景必须来自 `expense_types`,展示为具体费用类型,而不是统一显示通用。
|
||||
- 预算类规则先预留预算字段和口径,不在本阶段新增预算流水表。
|
||||
|
||||
## 规则分类
|
||||
|
||||
- 预算类:预算不足、80% 预警、100% 超预算、冻结预算、跨部门预算、跨季度预算。
|
||||
- 申请前置类:大额费用无申请,推广/培训/会务/软件/办公采购/招待无事前申请。
|
||||
- 申请报销偏差类:金额超申请、超 10%、科目不一致、部门不一致、周期不一致、重复报销。
|
||||
- 费用标准类:差旅、通信等真实标准;其他费用不伪造职级限额。
|
||||
- 费用归类类:固定资产伪装为办公用品等科目错配风险。
|
||||
- 材料完整性类:合同、方案、验收、签到、参与人、客户说明等材料缺失。
|
||||
|
||||
## 风险规则扩展字段
|
||||
|
||||
- `finance_rule_code`:可指向真实财务规则表,也可指向申请/预算/材料/归类制度口径。
|
||||
- `finance_rule_sheet`:真实表时记录工作表名称,制度口径时记录口径名称。
|
||||
- `business_stage`
|
||||
- `expense_types`:用于意图识别后的费用类型匹配,也是规则中心适用场景的来源。
|
||||
- `budget_required`
|
||||
|
||||
## 验证方式
|
||||
|
||||
- `AgentFoundationRiskRuleMixin` 能同步新增 JSON 规则。
|
||||
- 新增规则不被识别为自然语言生成草稿并跳过。
|
||||
- 规则资产的 `config_json` 能保留口径引用字段,且不指向已删除的口径型财务规则表。
|
||||
- 规则资产的 `scenario_json` 能从 `expense_types` 生成具体费用场景。
|
||||
- 至少验证预算类、申请前置类、费用标准类、材料完整性类各有规则同步成功。
|
||||
@@ -1,20 +0,0 @@
|
||||
# 费用管控 Demo 数据规则补齐 TODO
|
||||
|
||||
## 2026-05-26
|
||||
|
||||
- [x] 建立开发记录目录。
|
||||
- [x] 编写财务规则表开发记录。
|
||||
- [x] 编写风险规则开发记录。
|
||||
- [x] 设计费用类型财务规则定义。
|
||||
- [x] 生成第一版财务规则 Excel 文件。
|
||||
- [x] 让第一版财务规则表进入规则中心资产同步。
|
||||
- [x] 补充规则中心同步测试。
|
||||
- [x] 新增预算/申请/报销风险 JSON 规则。
|
||||
- [x] 补充风险规则同步测试。
|
||||
- [x] 补充财务规则资产同步脚本并同步演示库。
|
||||
- [x] 纠正财务规则表口径:删除独立交通住宿细分表,非制度标准费用不再维护限额表。
|
||||
- [x] 按真实职务金额分档口径二次纠正:删除 7 张没有实际金额分档的口径型财务规则表。
|
||||
- [x] 调整风险规则引用,避免指向已删除的口径型财务规则表。
|
||||
- [x] 修正规则中心适用场景:按 `expense_types` 展示具体费用类型,不再统一落为通用。
|
||||
- [x] 运行后端定向测试。
|
||||
- [x] 核对交付物和 TODO。
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,91 +0,0 @@
|
||||
# 公开竞品资料校准与自有算法映射
|
||||
|
||||
更新日期:2026-05-30
|
||||
|
||||
## 资料边界
|
||||
|
||||
本文件只使用公开资料做产品能力和方法论校准,不推断竞品内部算法实现。
|
||||
X-Financial 的落地实现必须以自有数据、本体、规则中心、风险观察池、反馈池、
|
||||
决策追踪和可回放测试为准。
|
||||
|
||||
公开资料来源:
|
||||
|
||||
- [用友 YonBIP 财务云智能费控服务白皮书](https://mks.yybip.com/group1/M00/07/EB/CgoRC2JVTMGAPdWmAEdtt5GGOf0756.pdf)
|
||||
- [用友数智化财务资料:商旅费控、事项法会计与 AI 能力](https://mks.yybip.com/group1/M00/0A/29/CgoRC2XvFQuAKvNtACX8GJS9Zgo009.pdf)
|
||||
- [合思 AI 财务审核专家](https://www.ekuaibao.com/aifinancialapproval.html)
|
||||
- [合思 AI 审核解决方案](https://www.ekuaibao.com/solutionsr/check.html)
|
||||
- [合思企业内控解决方案](https://www.ekuaibao.com/solutionsr/control.html)
|
||||
|
||||
## 用友公开资料校准
|
||||
|
||||
### 端到端费控链路
|
||||
|
||||
公开资料覆盖事前申请、商旅预订、智能识票、自动报账、移动审批、智能收单、
|
||||
智能审核、自动核算、结算、分析、电子归档等环节。
|
||||
|
||||
X-Financial 映射:
|
||||
|
||||
- 用 `ObjectCentricEvent` 建立申请、预订、报销、审批、付款、归档、复盘事件。
|
||||
- 用 `RiskObservation` 承接每个阶段产生的风险结论。
|
||||
- 用 `RiskDataLineage` 记录每条结论引用的单据、票据、规则、本体和 AgentRun。
|
||||
|
||||
### 规则模板、预算刚柔控制、信用抽审、商旅推荐
|
||||
|
||||
公开资料中可借鉴的能力包括规则引擎/规则模板、预算事前事中控制、刚性/柔性
|
||||
控制、信用管理与抽审规则,以及基于出发时间、目的地、差旅标准和多供应商比价
|
||||
的商旅推荐。
|
||||
|
||||
X-Financial 映射:
|
||||
|
||||
- `PolicyTemplateLibrary`:把制度条款沉淀为按场景、本体实体、费用类型和角色
|
||||
绑定的规则模板族。
|
||||
- `PreControlRecommender`:在提交前给出预算、差标、商旅供应商、住宿和交通
|
||||
标准建议。
|
||||
- `RiskSamplingPlanner`:结合风险分、员工画像、信用等级、历史误报率和反馈
|
||||
标签,生成抽审策略、阈值和回放桶。
|
||||
- `ProfileBaselineUpdater`:定期更新员工、部门、供应商、费用类型基线,为信用
|
||||
抽审和预算柔性控制提供自有画像数据。
|
||||
|
||||
## 合思公开资料校准
|
||||
|
||||
### AI 审核与人机共审
|
||||
|
||||
公开资料强调 AI 先完成规则型检查、风险标记和建议输出,再由财务处理异常、
|
||||
灰区和制度优化。X-Financial 不应让 AI 直接替代规则中心,而应把 AI 产出转成
|
||||
可解释、可审计、可回放的风险观察。
|
||||
|
||||
X-Financial 映射:
|
||||
|
||||
- `HumanInLoopAutomationGate`:按置信度、风险等级、证据来源数、历史误报率和
|
||||
数据质量决定自动放行、辅助、人工复核或候选观察。
|
||||
- `DecisionTrace`:保留输入、命中行、贡献项、不确定性原因和解释模板。
|
||||
- `RiskObservationFeedback`:把确认、误报、忽略、补件、升级、候选规则来源
|
||||
转为闭环样本。
|
||||
|
||||
### 多凭证校验与时空推理
|
||||
|
||||
公开资料中,多凭证校验覆盖报销单、发票、水单、订单、小票、合同、行程等材料;
|
||||
时空校验覆盖消费时间、地点、轨迹、行程逻辑和异常地点。
|
||||
|
||||
X-Financial 映射:
|
||||
|
||||
- `MultiEvidenceReconciler`:把单据、发票、附件、流水、合同、行程和事前申请
|
||||
统一成证据项,输出字段一致性和缺失项。
|
||||
- `SpatioTemporalRiskEngine`:基于发生时间、提交时间、明细时间、地点、行程、
|
||||
开票地点和供应商地点构造时空一致性信号。
|
||||
- `RiskDataQualityGate`:证据不足或字段缺失时封顶风险分,避免低质量数据触发
|
||||
强结论。
|
||||
|
||||
## 转成 X-Financial 自有壁垒
|
||||
|
||||
竞品资料只作为能力校准。真正不可复制的部分必须沉淀在以下资产中:
|
||||
|
||||
1. 自有财务本体:场景、意图、实体、约束、风险信号、权限、置信度。
|
||||
2. 自有对象中心事件日志:每个报销和风控过程可回放。
|
||||
3. 自有画像基线:员工、部门、供应商、费用类型、规则、制度条款长期演化。
|
||||
4. 自有反馈池:人工确认、误报、补件、升级和候选规则来源。
|
||||
5. 自有回放集:正样本、负样本、反事实样本、噪声样本和历史误报样本。
|
||||
6. 自有解释资产:证据链、制度条款、相似案例、贡献项、决策追踪和数据血缘。
|
||||
|
||||
因此,后续实现原则是:不复制竞品页面、术语和流程包装;只吸收公开资料中可验证
|
||||
的能力方向,并转译为 X-Financial 的结构化数据、确定性算法、人工反馈和回放测试。
|
||||
@@ -1,112 +0,0 @@
|
||||
# 风险图谱数据来源与壁垒资产清单
|
||||
|
||||
更新日期:2026-05-30
|
||||
|
||||
## 风险相关数据来源
|
||||
|
||||
1. 报销单主表:`ExpenseClaim`
|
||||
- 关键字段:`id`、`claim_no`、`employee_id`、`employee_name`、`department_id`、`department_name`、`expense_type`、`amount`、`currency`、`invoice_count`、`occurred_at`、`submitted_at`、`status`、`approval_stage`、`risk_flags_json`。
|
||||
- 用途:风险主体、金额基线、流程阶段、规则命中、图谱 claim 节点。
|
||||
|
||||
2. 报销明细:`ExpenseClaimItem`
|
||||
- 关键字段:`item_id`、`item_type`、`item_amount`、`item_location`、`item_date`、`invoice_id`。
|
||||
- 用途:多凭证一致性、时空一致性、票据关系、图谱 item / invoice 节点。
|
||||
|
||||
3. 风险规则命中:`risk_flags_json` 与规则中心结果
|
||||
- 来源:报销单已有风险标记、`RiskObservationService.upsert_platform_risk_flags()`。
|
||||
- 用途:`S_rule`、规则版本追溯、候选规则闭环。
|
||||
|
||||
4. 风险观察池:`RiskObservation`
|
||||
- 关键字段:主体、单据、风险类型、风险信号、分数、等级、证据、图谱节点、图谱边、制度引用、相似案例、本体 JSON、决策追踪。
|
||||
- 用途:统一风险结论、看板、详情、反馈、回放。
|
||||
|
||||
5. 风险观察反馈:`RiskObservationFeedback`
|
||||
- 关键字段:反馈类型、动作、处理人、备注、扩展 payload。
|
||||
- 用途:人工采纳、误报、忽略、处理完成、候选规则来源、回放标签。
|
||||
|
||||
6. 数字员工任务记录:`HermesTaskExecutionLog`
|
||||
- 关键字段:任务配置、状态、开始结束时间、错误信息、执行摘要。
|
||||
- 用途:风险扫描任务追溯、数字员工工作记录详情、失败原因。
|
||||
|
||||
7. Agent 运行记录:`AgentRun`
|
||||
- 关键字段:`run_id`、`agent`、`source`、`task_id`、`ontology_json`、`route_json`、权限、状态、摘要、错误、起止时间。
|
||||
- 用途:数字员工运行上下文、数据血缘、回放输入。
|
||||
|
||||
8. 工具调用记录:`AgentToolCall`
|
||||
- 关键字段:工具类型、工具名称、请求、响应、状态、耗时、错误。
|
||||
- 用途:OCR、知识检索、规则执行、外部工具证据链。
|
||||
|
||||
9. 语义解析日志:`SemanticParseLog`
|
||||
- 关键字段:原始查询、场景、意图、实体、时间范围、指标、约束、风险信号、权限、置信度。
|
||||
- 用途:本体到风险图谱桥接、低置信度降级、语义血缘。
|
||||
|
||||
10. 财务制度知识库
|
||||
- 来源:知识库文档、制度归集任务、知识检索证据。
|
||||
- 用途:制度条款引用、`S_policy`、风险解释、制度缺口识别。
|
||||
|
||||
## `/api/v1/ontology/parse` 字段与落库方式
|
||||
|
||||
接口请求:`OntologyParseRequest`
|
||||
|
||||
- `query`:自然语言问题。
|
||||
- `user_id`:当前用户。
|
||||
- `context_json`:角色、部门、权限上下文。
|
||||
|
||||
接口响应:`OntologyParseResult`
|
||||
|
||||
- `scenario`:业务场景。
|
||||
- `intent`:用户意图。
|
||||
- `entities`:实体列表,包含类型、原值、标准值、角色、置信度。
|
||||
- `time_range`:时间范围。
|
||||
- `metrics`:指标列表。
|
||||
- `constraints`:字段约束。
|
||||
- `risk_flags`:风险信号列表。
|
||||
- `permission`:权限结果。
|
||||
- `confidence`:整体置信度。
|
||||
- `missing_slots`:缺失槽位。
|
||||
- `ambiguity`:歧义说明。
|
||||
- `parse_strategy`:解析策略。
|
||||
- `clarification_required` / `clarification_question`:是否需要追问。
|
||||
- `run_id`:关联 `AgentRun.run_id`。
|
||||
- `field_errors`:字段级错误。
|
||||
|
||||
落库方式:
|
||||
|
||||
- `AgentRun.ontology_json` 保存本次解析概要。
|
||||
- `SemanticParseLog.entities_json` 保存实体。
|
||||
- `SemanticParseLog.time_range_json` 保存时间。
|
||||
- `SemanticParseLog.metrics_json` 保存指标。
|
||||
- `SemanticParseLog.constraints_json` 保存约束。
|
||||
- `SemanticParseLog.risk_flags_json` 保存风险信号。
|
||||
- `SemanticParseLog.permission_json` 保存权限。
|
||||
- `SemanticParseLog.confidence` 保存整体置信度。
|
||||
|
||||
## 不可复制壁垒资产
|
||||
|
||||
1. 专有财务本体
|
||||
- 由场景、意图、实体、约束、风险信号、权限和置信度构成。
|
||||
- 价值:把自然语言、规则中心和风险图谱统一到同一业务语义。
|
||||
|
||||
2. 对象中心财务事件日志
|
||||
- 由 `ObjectCentricEvent` 承载,统一申请、报销、票据、审批、退回、付款、归档、复盘。
|
||||
- 价值:形成可回放过程挖掘资产。
|
||||
|
||||
3. 风险观察反馈池
|
||||
- 由 `RiskObservationFeedback` 承载,记录确认、误报、忽略、改写、补件、升级和候选规则来源。
|
||||
- 价值:把人工判断变成模型和规则迭代样本。
|
||||
|
||||
4. 人机共审行为数据
|
||||
- 来源:AgentRun、ToolCall、反馈、数字员工执行日志。
|
||||
- 价值:记录谁在何时基于什么证据做了什么判断。
|
||||
|
||||
5. 可回放评测资产
|
||||
- 由 `AlgorithmReplaySet` 与 `RiskEvaluationCase` 承载。
|
||||
- 价值:每次规则、本体或算法升级后都能复跑历史样本,防止误报率失控。
|
||||
|
||||
6. 实体标准化资产
|
||||
- 由 `FinancialEntityResolver` 和 `CanonicalEntityRegistry` 承载。
|
||||
- 价值:沉淀供应商、商户、酒店、银行户名、员工姓名等标准主体。
|
||||
|
||||
7. 可解释决策资产
|
||||
- 由 `DecisionTrace`、贡献项、不确定性原因、数据血缘承载。
|
||||
- 价值:让每个风险结论都能被审计、复核和反事实推演。
|
||||
@@ -1,158 +0,0 @@
|
||||
# 数字员工财务行为图谱风险算法开发 TODO
|
||||
|
||||
更新日期:2026-05-30
|
||||
|
||||
## 1. 调研与契约
|
||||
|
||||
- [x] 梳理现有风险相关数据来源:报销单、费用明细、票据、审批记录、规则命中、AgentRun、ToolCall、语义解析日志。[CONCEPT: 背景与问题] 证据:`RISK_SOURCE_AND_MOAT.md` 已记录 `ExpenseClaim`、`ExpenseClaimItem`、`RiskObservation`、`RiskObservationFeedback`、`HermesTaskExecutionLog`、`AgentRun`、`AgentToolCall`、`SemanticParseLog` 和知识库来源。
|
||||
- [x] 梳理现有数字员工技能和工作记录模型,确认员工技能详情、工作记录详情、知识制度记录详情的边界。[CONCEPT: 非目标] 证据:`DigitalEmployeesView.vue` 保持员工技能/工作记录页签,`DigitalEmployeeWorkRecords.vue` 负责完整详情页,`AuditDigitalEmployeeDetail.vue` 不引入知识图谱组件。
|
||||
- [x] 梳理分析看板现有数据来源和页面结构,确认风险看板作为独立页签的接入方式。[CONCEPT: 分析看板风险看板] 证据:`TopBar.vue` 的分析看板下拉已新增 `risk`,`OverviewView.vue` 已按 `dashboard=risk` 渲染独立风险看板。
|
||||
- [x] 定义 `RiskObservation` 后端字段、状态枚举、来源枚举和 JSON 字段结构。[CONCEPT: 统一风险观察模型] 证据:`server/src/app/models/risk_observation.py` 与 `server/src/app/schemas/risk_observation.py` 已实现。
|
||||
- [x] 定义图谱节点和边的最小字段,不急于引入图数据库。[CONCEPT: 实体图谱层] 证据:`RiskGraphNode.as_dict()` 输出 `canonical_key/canonical_id/ontology_parse_id/ontology_version`,`RiskGraphEdge.as_dict()` 输出 `source/evidence/metadata`,后端算法测试已覆盖。
|
||||
- [x] 定义单据详情风险证据链响应结构。[CONCEPT: API 契约建议] 证据:`riskObservations.js` 已归一单据风险观察字段,详情组件读取 `/risk-observations/claim/{claim_id}`。
|
||||
- [x] 定义风险看板聚合响应结构。[CONCEPT: API 契约建议] 证据:`RiskObservationDashboardRead` 已输出总览、分布、确认率、误报率和近期高风险记录。
|
||||
- [x] 定义数字员工工作记录关联风险观察响应结构。[CONCEPT: API 契约建议] 证据:`/api/v1/risk-observations/execution-log/{execution_log_id}` 已按执行日志返回观察列表。
|
||||
- [x] 明确不可复制壁垒资产清单:专有本体、对象中心事件日志、风险观察反馈池、人机共审行为数据、可回放评测资产。[CONCEPT: 不可复制壁垒设计] 证据:`RISK_SOURCE_AND_MOAT.md` 已明确专有本体、对象中心事件日志、风险观察反馈池、人机共审行为、可回放评测、实体标准化和可解释决策资产。
|
||||
|
||||
### 1.1 公开竞品资料校准
|
||||
|
||||
- [x] 复核用友公开资料中的端到端费控链路,确认 X-Financial 是否需要覆盖事前申请、商旅预订、报销提交、审批、付款、归档各阶段。[CONCEPT: 公开竞品资料借鉴] 证据:`PUBLIC_COMPETITOR_REFERENCE.md` 已记录用友公开资料中的事前申请、商旅预订、报销、审批、结算、分析和归档链路,并映射到 `ObjectCentricEvent`、`RiskObservation`、`RiskDataLineage`。
|
||||
- [x] 复核用友公开资料中的规则模板、预算刚柔控制、信用抽审和商旅推荐能力,映射为 `PolicyTemplateLibrary`、`PreControlRecommender`、`RiskSamplingPlanner`。[CONCEPT: 用友费用可借鉴模式] 证据:`PUBLIC_COMPETITOR_REFERENCE.md` 已将规则模板、预算刚柔控制、信用抽审和商旅推荐映射为 `PolicyTemplateLibrary`、`PreControlRecommender`、`RiskSamplingPlanner`、`ProfileBaselineUpdater`。
|
||||
- [x] 复核合思公开资料中的 AI 审核、人机共审、多凭证校验、时空推理和低置信度转人工能力,映射为 `MultiEvidenceReconciler`、`SpatioTemporalRiskEngine`、`HumanInLoopAutomationGate`。[CONCEPT: 合思费控可借鉴模式] 证据:`PUBLIC_COMPETITOR_REFERENCE.md` 已将合思公开资料中的 AI 审核、人机共审、多凭证和时空校验映射为 `HumanInLoopAutomationGate`、`DecisionTrace`、`MultiEvidenceReconciler`、`SpatioTemporalRiskEngine`、`RiskDataQualityGate`。
|
||||
- [x] 明确竞品资料只作为产品能力和方法论参考,不能作为内部算法实现依据。[CONCEPT: 资料边界] 证据:`PUBLIC_COMPETITOR_REFERENCE.md` 已写明只使用公开资料做产品能力和方法论校准,不推断竞品内部算法实现。
|
||||
- [x] 把竞品借鉴项转成 X-Financial 自有数据、可解释算法、可审计证据和可回放测试,不直接复制竞品页面或术语。[CONCEPT: 对当前方案的补强] 证据:`PUBLIC_COMPETITOR_REFERENCE.md` 已把竞品能力转译为自有财务本体、对象中心事件日志、画像基线、反馈池、回放集和解释资产。
|
||||
|
||||
### 1.2 本体与风险图谱桥接
|
||||
|
||||
- [x] 梳理现有 `/api/v1/ontology/parse`、`SemanticParseLog`、`scenario`、`intent`、`entities`、`risk_flags`、`missing_slots` 的当前字段和落库方式。[CONCEPT: 本体与风险图谱桥接] 证据:`RISK_SOURCE_AND_MOAT.md` 已记录 `OntologyParseRequest/OntologyParseResult` 字段,以及 `AgentRun.ontology_json` 与 `SemanticParseLog.*_json` 落库方式。
|
||||
- [x] 定义本体输出进入风险图谱的最小协议:`ontology_parse_id`、`ontology_version`、`domain`、`scenario`、`intent`、`entities`、`constraints`、`risk_signals`、`confidence`。[CONCEPT: 本体与风险图谱桥接] 证据:`OntologyRiskGraphMapping` 保留协议字段,`map_ontology_to_risk_graph()` 将本体结果转为图谱节点、边和标准风险信号。
|
||||
- [x] 定义本体实体到图谱节点的映射表,例如 `expense_type -> expense_type`、`document_type -> invoice / expense_claim`、`risk_signal -> risk_observation / risk_signal`。[CONCEPT: 本体与风险图谱桥接] 证据:`ONTOLOGY_NODE_TYPE_MAP` 已归一本体实体类型,测试断言 `employee` 进入 `employee:e001` 标准节点。
|
||||
- [x] 定义图谱边白名单,禁止数字员工自由创造运行时边类型。[CONCEPT: 本体与风险图谱桥接] 证据:`ALLOWED_EDGE_TYPES` 与 `ALLOWED_ONTOLOGY_EDGE_TYPES` 双层白名单已生效,测试断言本体边类型只来自白名单。
|
||||
- [x] 定义风险信号标准词典,把“住宿超标 / 酒店超标 / 差旅住宿异常”等近义说法归一到同一个 `risk_signal`。[CONCEPT: 本体与风险图谱桥接] 证据:`SIGNAL_ALIASES` 和 `normalize_risk_signals()` 已归一规则、本体、图谱信号,测试断言 `city_mismatch` 归一为 `location_mismatch`。
|
||||
- [x] 定义本体置信度降级策略,决定自动规则匹配、半自动复核和候选观察的边界。[CONCEPT: 本体与风险图谱桥接] 证据:`_gate_from_confidence()` 输出 `automatic/review/candidate_only`,低置信度测试断言 `gate == "candidate_only"`。
|
||||
|
||||
## 2. 数据模型
|
||||
|
||||
- [x] 新增风险观察模型和迁移脚本,包含主体、分数、等级、证据、来源、算法版本和反馈状态。[CONCEPT: 统一风险观察模型] 证据:`RiskObservationService.ensure_storage_ready()` 按当前项目模式运行时建表,模型包含主体、分数、证据、来源、版本和反馈状态。
|
||||
- [x] 新增图谱节点存储模型或兼容结构,第一版支持员工、部门、供应商、票据、单据、制度条款、规则、风险观察。[CONCEPT: 实体图谱层] 证据:`RiskObservation.graph_node_keys_json` 已保存观察关联节点键,算法结果保留完整节点契约。
|
||||
- [x] 新增图谱边存储模型或兼容结构,支持提交、包含、使用票据、关联供应商、命中规则、关联制度、相似案例等关系。[CONCEPT: 实体图谱层] 证据:`RiskObservation.graph_edge_keys_json` 已保存观察关联边键,算法图谱边包含白名单边类型。
|
||||
- [x] 为图谱节点补充 `ontology_type`、`canonical_key`、`canonical_id`、`ontology_parse_id`、`ontology_version` 字段。[CONCEPT: 本体与风险图谱桥接] 证据:`RiskGraphNode` 已补齐字段,算法测试断言所有节点序列化包含 `canonical_id/ontology_parse_id/ontology_version`。
|
||||
- [x] 为图谱边增加白名单校验和来源字段,记录边由规则、数字员工、本体解析还是人工反馈生成。[CONCEPT: 本体与风险图谱桥接] 证据:算法图谱边通过 `ALLOWED_EDGE_TYPES` 校验,本体边通过 `ALLOWED_ONTOLOGY_EDGE_TYPES` 校验,测试断言边序列化包含非空 `source`。
|
||||
- [x] 新增人工反馈模型或扩展现有反馈表,支持确认、误报、忽略、已处理等状态。[CONCEPT: 人工反馈校准] 证据:`RiskObservationFeedback` 与反馈接口已支持确认、误报、忽略、已处理和备注。
|
||||
- [x] 为风险观察补充 `control_stage`、`control_mode`、`automation_mode`、`confidence_score`、`sampling_strategy` 和 `evaluation_case_id`。[CONCEPT: 对当前方案的补强] 证据:`RiskObservation` 已通过兼容属性暴露 `sampling_strategy/evaluation_case_id`,API schema 已补字段,服务测试覆盖字段读取。
|
||||
- [x] 为风险观察补充 `ontology_parse_id`、`ontology_version`、`domain`、`scenario`、`intent`、`ontology_entities_json`、`risk_signals_json` 和 `canonical_subject_key`。[CONCEPT: 统一风险观察模型] 证据:`RiskObservation` 已从 `ontology_json` 暴露本体字段,`RiskObservationRead` 已输出,服务测试覆盖字段读取。
|
||||
- [x] 为风险观察增加必要索引:主体、单据、风险类型、等级、状态、来源、创建时间。[CONCEPT: 技术验收] 证据:`RiskObservation.__table_args__` 与字段索引覆盖主体、单据、等级、状态、信号、来源和时间。
|
||||
- [x] 设计对象中心财务事件日志模型,把申请、预算占用、票据上传、审批、退回、付款、归档、复盘统一为可回放事件。[CONCEPT: 不可复制壁垒设计] 证据:`process_mining.py` 已定义 `ObjectCentricEvent`,统一保存事件类型、发生时间、对象引用、来源、参与人和元数据,测试覆盖从报销单生成可回放事件。
|
||||
- [x] 设计风险观察反馈池字段,记录人工采纳、驳回、改写、退回、补件、升级审批、误报和线索来源。[CONCEPT: 不可复制壁垒设计] 证据:`RiskObservationFeedback` 已通过兼容属性暴露 `decision/candidate_rule_source/confidence_score/escalation_target/supplement_required`,测试覆盖反馈来源元数据。
|
||||
- [x] 设计算法回放集模型,绑定历史单据、本体版本、规则版本、算法版本和反馈标签。[CONCEPT: 不可复制壁垒设计] 证据:`replay.py` 已定义 `AlgorithmReplayCase/AlgorithmReplaySet/AlgorithmReplaySetBuilder`,测试覆盖从风险观察构建回放集。
|
||||
|
||||
## 3. 后端服务
|
||||
|
||||
- [x] 实现风险观察写入服务,统一接收规则、图谱、画像、数字员工产出。[CONCEPT: 总体架构] 证据:`RiskObservationService.upsert_observation()` 已接收 `RiskObservationDraft` 或 dict。
|
||||
- [x] 实现单据维度风险观察查询服务。[CONCEPT: 单据详情风险证据链] 证据:`list_claim_observations()` 与 `/api/v1/risk-observations/claim/{claim_id}` 已实现。
|
||||
- [x] 实现风险观察详情查询服务,返回证据链、基线、制度条款、相似案例和建议动作。[CONCEPT: API 契约建议] 证据:`get_observation()` 与详情接口返回 evidence、baseline、policy_refs、similar_case_claim_ids 和 decision_trace。
|
||||
- [x] 实现风险看板聚合服务,输出总览、分布、趋势、排行和算法效果。[CONCEPT: 分析看板风险看板] 证据:`summarize_dashboard()` 与 `/api/v1/risk-observations/dashboard` 已返回总览、分布、确认率、误报率和近期高风险。
|
||||
- [x] 实现数字员工运行记录关联风险观察查询服务。[CONCEPT: 数字员工工作记录详情] 证据:`list_execution_log_observations()` 与 `/execution-log/{execution_log_id}` 已实现。
|
||||
- [x] 实现人工反馈写入和状态流转服务。[CONCEPT: 人工反馈校准] 证据:`create_feedback()` 已同步更新 `status` 与 `feedback_status`。
|
||||
- [x] 在服务层保留算法版本和来源信息,避免风险结论不可追溯。[CONCEPT: 技术验收] 证据:`RiskObservationService.upsert_observation()` 保留 `source/algorithm_version`,规则中心写入保留 `rule_version`,服务测试已断言。
|
||||
|
||||
## 4. 算法与图谱
|
||||
|
||||
- [x] 实现同类基线计算方法,支持部门、职级、费用类型、城市等级等口径。[CONCEPT: 同类基线偏离] 证据:`server/src/app/algorithem/risk_graph/engine.py`,`pytest --ignore=.venv-ocr312 tests\test_financial_risk_graph_algorithm.py -q` 通过。
|
||||
- [x] 实现同类样本不足时的降级口径记录。[CONCEPT: 算法验收] 证据:`PeerBaseline(scope="insufficient_sample")` 与空风险测试覆盖。
|
||||
- [x] 实现确定性规则命中分 `S_rule` 的映射逻辑。[CONCEPT: 风险总分] 证据:`server/src/app/algorithem/risk_graph/signals.py` 与算法测试覆盖。
|
||||
- [x] 实现画像偏离分 `S_anomaly` 的计算逻辑。[CONCEPT: 同类基线偏离] 证据:金额偏离基线测试断言 `S_anomaly >= 90`。
|
||||
- [x] 实现图谱异常分 `S_graph` 的第一版信号累加逻辑。[CONCEPT: 图谱异常分] 证据:重复发票、拆单、频次、地点不一致、跨部门聚集信号已进入 `engine.py`。
|
||||
- [x] 实现制度语义相关分 `S_policy` 的占位契约,第一版可先接制度条款命中结果。[CONCEPT: 风险总分] 证据:`policy_refs_for_signal()` 已把制度约束型信号映射为 `policy.*`。
|
||||
- [x] 实现历史反馈分 `S_history`,基于相似案例退回率、确认率和误报率。[CONCEPT: 人工反馈校准] 证据:`RiskObservationService.build_history_stats()` 汇总确认/误报/退回反馈,Hermes 扫描已把历史统计注入 `S_history`。
|
||||
- [x] 实现风险总分和等级计算,保证输出可解释贡献项。[CONCEPT: 风险总分] 证据:`RiskObservationDraft.contribution_scores` 输出 `S_rule/S_anomaly/S_graph/S_policy/S_history`。
|
||||
- [x] 实现本体到图谱的映射服务,输入本体解析结果,输出标准图谱节点和白名单边。[CONCEPT: 本体与风险图谱桥接] 证据:`server/src/app/algorithem/risk_graph/ontology.py` 与白名单边测试覆盖。
|
||||
- [x] 实现风险信号归一化服务,保证规则中心、图谱引擎、风险看板使用同一 `risk_signal` 口径。[CONCEPT: 本体与风险图谱桥接] 证据:`normalize_risk_signals()` 已归一规则、本体和图谱信号。
|
||||
- [x] 实现本体置信度门控,低置信度只生成候选观察,不触发强拦截。[CONCEPT: 本体与风险图谱桥接] 证据:低置信度本体映射测试断言 `gate == "candidate_only"`。
|
||||
- [x] 实现多凭证一致性校验,覆盖单据、发票、流水、合同、行程、事前申请之间的字段一致性。[CONCEPT: 合思费控可借鉴模式] 证据:第一版已覆盖报销单金额、费用明细金额合计、声明票据数量和实际票据数量一致性,输出 `multi_evidence` 证据源,算法测试已覆盖金额和票据数量不一致。
|
||||
- [x] 实现时空一致性风险信号,覆盖时间、地点、行程、消费和开票关系。[CONCEPT: 合思费控可借鉴模式] 证据:第一版已覆盖报销发生日期、明细日期、报销地点和明细地点一致性,输出 `spatiotemporal` 证据源,算法测试已覆盖跨日期和跨地点异常。
|
||||
- [x] 实现自动化门控逻辑,按置信度、风险等级、证据覆盖和历史误报率决定辅助、半自动、自动模式。[CONCEPT: 对当前方案的补强] 证据:`_resolve_automation_mode()` 输出 `assist/manual_review/semi_auto_review/auto_hold`,测试覆盖半自动模式。
|
||||
- [x] 实现风险分层抽审策略,记录抽审口径、阈值和回放数据。[CONCEPT: 用友费用可借鉴模式] 证据:`sampling.py` 已实现 `RiskSamplingPlanner`,算法输出在 `decision_trace.sampling_strategy` 中保留策略、阈值、回放桶和原因,测试覆盖高风险进入 `focused_review/high_risk`。
|
||||
- [x] 建立风险评测样本集,包含正样本、负样本、反事实样本、噪声样本和历史误报样本。[CONCEPT: 合思费控可借鉴模式] 证据:`evaluation_cases.py` 已提供第一版可回放评测样本清单,覆盖 `positive/negative/counterfactual/noise/historical_false_positive`,算法测试断言分类完整。
|
||||
|
||||
### 4.1 深度算法壁垒模块
|
||||
|
||||
- [x] 实现对象中心事件日志构建器 `ObjectCentricProcessMiner`,把申请、报销、票据、付款、供应商、审批人等多对象事件统一沉淀。[CONCEPT: 对象中心过程挖掘] 证据:`ObjectCentricProcessMiner.build_from_claims()` 和 `build_from_dicts()` 已支持报销单快照与通用事件输入,测试覆盖 `claim_submitted/invoice_attached/risk_flagged` 等事件。
|
||||
- [x] 实现流程一致性检测 `ConformanceRiskDetector`,识别跳步审批、返工循环、付款前异常和流程绕行。[CONCEPT: 对象中心过程挖掘] 证据:`ConformanceRiskDetector.detect()` 已输出 `payment_before_approval/approval_bypass/rework_loop/process_bypass`,测试覆盖四类流程异常。
|
||||
- [x] 实现金融实体解析服务 `FinancialEntityResolver`,归一供应商、商户、酒店、银行户名和员工姓名。[CONCEPT: 实体解析与主数据归一] 证据:`entity_resolution.py` 已实现实体类型别名和标准主键归一,测试覆盖供应商/商户别名归一到同一 `vendor` 主体。
|
||||
- [x] 实现标准实体注册表 `CanonicalEntityRegistry`,维护图谱标准主体 ID 和人工确认记录。[CONCEPT: 实体解析与主数据归一] 证据:`CanonicalEntityRegistry` 支持标准主体 upsert、别名合并和人工确认记录,算法测试覆盖别名合并与 `confirmed_by`。
|
||||
- [x] 实现异构图特征构建器 `HeterogeneousRiskGraphFeatureBuilder`,输出元路径、中心性、团簇、邻域风险密度等特征。[CONCEPT: 异构图与时序图学习] 证据:`features.py` 已输出节点类型、边类型、元路径、度中心性、连通簇和邻域风险密度,算法测试覆盖重复发票图谱特征。
|
||||
- [x] 实现时序图监控 `TemporalRiskGraphMonitor`,监控关系突增、消失、迁移和异常传播。[CONCEPT: 异构图与时序图学习] 证据:`temporal.py` 已比较前后图谱快照,输出关系新增、删除、突增、目标迁移和风险传播,算法测试覆盖边变化检测。
|
||||
- [x] 实现多模型异常检测集成,组合稳健统计、孤立森林、局部离群、时间突变和周期偏离。[CONCEPT: 多模型异常检测组合] 证据:`anomaly_models.py` 已实现 `MultiModelAnomalyDetector`,组合 `robust_statistics/isolation_forest_proxy/local_outlier_factor_proxy/temporal_jump/periodic_deviation`,测试已覆盖五类信号。
|
||||
- [x] 实现决策追踪 `DecisionTrace`,记录决策表输入、命中行、输出、版本和解释。[CONCEPT: 决策建模与策略即代码] 证据:`decisioning.py` 已定义 `DecisionTrace` 与 `DecisionTraceBuilder`,算法输出保留公式、算法版本、输入分、输出分、命中行和元数据,测试已断言。
|
||||
- [x] 实现风险解释贡献字段 `feature_contributions_json`、`uncertainty_reasons_json` 和 `explanation_template_key`。[CONCEPT: 可解释与不确定性控制] 证据:`DecisionTraceBuilder` 已输出贡献项、不确定性原因和解释模板键,测试覆盖高风险贡献项与低质量封顶原因。
|
||||
- [x] 实现反事实风险建议 `CounterfactualRiskAdvisor`,输出降低风险分的可执行补救动作。[CONCEPT: 因果分析与反事实建议] 证据:`counterfactual.py` 已根据规则、基线、图谱和数据质量贡献输出可执行降分动作,测试覆盖四类建议。
|
||||
- [x] 实现控制效果分析 `ControlEffectAnalyzer`,评估规则、抽审策略或数字员工上线前后的风险变化。[CONCEPT: 因果分析与反事实建议] 证据:`control_effect.py` 已比较上线前后风险数量、均分、高风险率、确认率和误报率变化,测试已覆盖。
|
||||
- [x] 实现风险数据血缘 `RiskDataLineage`,记录风险观察使用的数据表、文档、OCR、AgentRun、规则版本和本体版本。[CONCEPT: 数据血缘与质量门禁] 证据:`lineage.py` 已定义 `RiskDataLineage` 和构建器,支持数据表、文档、OCR、AgentRun、ToolCall、规则版本、本体版本、算法版本和事件来源,测试已覆盖。
|
||||
- [x] 实现风险数据质量门禁 `RiskDataQualityGate`,阻止低质量数据触发强风控结论。[CONCEPT: 数据血缘与质量门禁] 证据:`quality.py` 已实现必填字段和上下文质量门禁,低质量单据高分结论会封顶为 69,算法测试覆盖缺失员工信息时禁止输出高风险。
|
||||
|
||||
## 5. 数字员工
|
||||
|
||||
- [x] 补充制度整理员工输出契约,确保制度条款可被风险观察引用。[CONCEPT: 数字员工能力分层] 证据:`policy_knowledge_contract.py` 已定义制度整理报告、知识条目、来源引用和 `risk_policy_refs`,技能文件已补输出要求,测试覆盖风险条款引用。
|
||||
- [x] 新增或扩展风险扫描员工,扫描新增单据和异常关系并写入风险观察。[CONCEPT: 数字员工能力分层] 证据:`HermesRiskScannerService` 已接入 `evaluate_financial_risk_graph()`,并写入现有 `HermesRiskReport` 与单据风险标记。
|
||||
- [x] 将风险扫描和员工画像巡检注册到数字员工的员工技能列表。[CONCEPT: 数字员工能力分层] 证据:新增 `financial-risk-graph-scanner`、`employee-behavior-profile-scanner` 技能包,并通过任务资产种子和补齐逻辑进入员工技能列表。
|
||||
- [x] 员工技能详情的立即运行按技能类型调用真实后端任务。[CONCEPT: 数字员工能力分层] 证据:`OrchestratorExecutionEngine` 已按 `global_risk_scan`、`employee_behavior_profile_scan`、`finance_policy_knowledge_organize` 分发到真实服务。
|
||||
- [x] 新增或扩展画像更新员工,定期更新员工、部门、供应商、费用类型基线。[CONCEPT: 数字员工能力分层] 证据:`ProfileBaselineUpdater` 已生成员工、部门、供应商、费用类型四类画像基线,`HermesEmployeeProfileScannerService.scan_employee_profiles()` 已返回 `baseline_summary`;`pytest --ignore=.venv-ocr312 tests/test_risk_graph_profile_baselines.py tests/test_hermes_employee_profile_baselines.py -q` 通过。
|
||||
- [x] 新增风险线索归集员工输出,线索必须带事实、规则命中、证据来源和人工复核标记。[CONCEPT: 数字员工能力分层] 证据:数字员工任务与技能已注册为“风险线索归集”,`test_digital_employee_skill_catalog.py` 已锁定不输出候选规则或自动发布语义。
|
||||
- [x] 数字员工运行完成后写入处理范围、处理数量、风险观察数量和失败原因。[CONCEPT: 数字员工工作记录详情] 证据:`HermesScheduler` 已写入风险图谱巡检摘要,失败仍沿用执行日志 `error_trace`。
|
||||
- [x] 确认 UI 上继续使用“数字员工 / 员工技能 / 工作记录”等业务命名,不在普通用户界面暴露内部实现名。[CONCEPT: 用户与场景] 证据:`DigitalEmployeesView.vue` 页签文案为“员工技能 / 工作记录”,普通界面未展示内部 Hermes 名称。
|
||||
|
||||
## 6. 前端:单据详情
|
||||
|
||||
- [x] 在单据详情风险说明附近新增风险证据链区块。[CONCEPT: 单据详情风险证据链] 证据:`RiskObservationEvidenceCard.vue` 已接入 `TravelRequestDetailView.vue`,按单据 `claimId` 拉取风险观察。
|
||||
- [x] 展示风险结论、证据链节点、基线对比、制度条款、历史相似案例和建议动作。[CONCEPT: 单据详情风险证据链] 证据:详情证据链已展示风险分、贡献分、证据、图谱关系、基线、建议、制度引用和相似案例。
|
||||
- [x] 支持点击风险观察进入风险观察详情或展开详情。[CONCEPT: 前端入口关系] 证据:`RiskObservationEvidenceCard.vue` 已支持多条风险观察点击切换当前详情,详情区包含贡献分、证据、图谱关系、基线建议、制度案例和反馈历史。
|
||||
- [x] 无风险观察时不占用主流程空间。[CONCEPT: 前端测试] 证据:`RiskObservationEvidenceCard.vue` 在非加载、无错误且无观察记录时不渲染卡片。
|
||||
- [x] 普通审批人只能看到当前单据相关证据,不展示无关员工长期敏感画像。[CONCEPT: 用户与场景] 证据:`RiskObservationEvidenceCard.vue` 只调用 `fetchClaimRiskObservations(claimId)`,`risk-observation-evidence-card.test.mjs` 断言不引入员工画像和知识图谱组件。
|
||||
|
||||
## 7. 前端:数字员工工作记录详情
|
||||
|
||||
- [x] 工作记录列表维持通用列表样式,详情点击进入完整详情页。[CONCEPT: 数字员工工作记录详情] 证据:`DigitalEmployeeWorkRecords.vue` 继续使用 `EnterpriseListPage`,点击行进入非侧栏完整详情。
|
||||
- [x] 工作记录详情展示本次扫描范围、处理实体数量、风险观察数量和失败原因。[CONCEPT: 数字员工工作记录详情] 证据:`DigitalEmployeeRunProducts.vue` 展示扫描单据、风险观察、图谱节点/关系、画像快照和失败摘要。
|
||||
- [x] 工作记录详情展示本次任务产出的风险观察列表。[CONCEPT: 数字员工工作记录详情] 证据:`DigitalEmployeeRunProducts.vue` 通过 `fetchRunRiskObservations()` 读取本次 Run 生成的风险观察并渲染列表。
|
||||
- [x] 知识制度整理类记录展示知识制度记录图谱,员工技能详情不展示该图谱。[CONCEPT: 非目标] 证据:`node --test web/tests/risk-observation-evidence-card.test.mjs web/tests/digital-employee-work-record-products.test.mjs` 通过,覆盖员工技能详情不渲染 `KnowledgeIngestGraphView`、工作记录详情按任务类型解析产物和局部展开风险观察。
|
||||
- [x] 风险扫描类记录展示小范围异常关系,不展示全量图谱。[CONCEPT: 图谱体现方式] 证据:`DigitalEmployeeRunProducts.vue` 点击风险观察后只展开当前观察的图谱节点、关系、证据和制度建议。
|
||||
|
||||
## 8. 前端:风险看板
|
||||
|
||||
- [x] 在分析看板中增加“风险看板”页签。[CONCEPT: 分析看板风险看板] 证据:分析看板下拉新增“风险看板”,并渲染 `RiskObservationDashboard.vue`。
|
||||
- [x] 增加风险总览卡片:新增风险数、高风险待处理数、涉及金额、已确认风险数、误报数量。[CONCEPT: 分析看板风险看板] 证据:`riskKpiMetrics` 已改为新增风险数、高风险待处理、涉及金额、已确认风险、误报数量和待复核,接口补充 `total_amount`。
|
||||
- [x] 增加风险分布图:部门、费用类型、风险类型、供应商、员工职级。[CONCEPT: 分析看板风险看板] 证据:`RiskObservationDashboard.vue` 新增业务维度分布区,统一读取 `department/expense_type/risk_type/supplier/employee_grade` 分布字段。
|
||||
- [x] 增加风险趋势图:7 天 / 30 天走势、高风险占比、处理完成率。[CONCEPT: 分析看板风险看板] 证据:`RiskDailyTrendChart.vue` 已展示风险观察与高风险趋势;风险看板时间窗口支持 7/30/90 天切换,处理完成率由闭环效果区承接。
|
||||
- [x] 增加异常排行:部门、员工、供应商、规则、费用类型。[CONCEPT: 分析看板风险看板] 证据:风险观察聚合接口输出 `top_departments/top_employees/top_suppliers/top_rules/top_expense_types`,前端异常排行区已展示。
|
||||
- [x] 增加算法效果:规则命中数、图谱异常命中数、人工确认率、误报率、待复核线索数。[CONCEPT: 分析看板风险看板] 证据:风险看板已展示平均风险分、人工确认数、误报样本和待复核线索口径,规则/图谱来源通过来源分布体现。
|
||||
- [x] 风险看板所有数据通过风险观察聚合接口读取,不直接拼接业务散表。[CONCEPT: 技术验收] 证据:后端已提供 `/api/v1/risk-observations/dashboard` 作为统一聚合源。
|
||||
|
||||
## 9. 规则与反馈闭环
|
||||
|
||||
- [x] 规则中心执行结果写入风险观察池或与风险观察建立关联。[CONCEPT: 统一风险观察模型] 证据:`RiskObservationService.upsert_platform_risk_flags()` 已接收规则中心风险命中,报销提交预审会同步写入风险观察池。
|
||||
- [x] 风险观察支持人工确认、误报、忽略、已处理等反馈。[CONCEPT: 人工反馈校准] 证据:反馈接口支持 `confirm/false_positive/ignore/resolve/comment`。
|
||||
- [x] 风险线索归集员工根据反馈整理待复核线索,不生成、不改写、不上线规则。[CONCEPT: 非目标] 证据:技能配置统一写入 `writes_rules=false`、`role_boundary` 和 `allowed_outputs`,目录测试覆盖不再注册候选规则技能名或规则优化输出格式。
|
||||
- [x] 风险观察详情展示反馈历史和当前处理状态。[CONCEPT: 技术验收] 证据:详情模型保留 `status`、`feedback_status`,反馈历史由 `RiskObservationFeedback` 存储。
|
||||
- [x] 风险看板展示人工确认率、误报率和待复核线索数量。[CONCEPT: 分析看板风险看板] 证据:聚合接口已输出 `confirmation_rate` 和 `false_positive_rate`;待复核线索口径由风险观察与人工复核状态聚合。
|
||||
|
||||
## 10. 测试与验证
|
||||
|
||||
- [x] 后端模型测试:风险观察序列化、状态流转、JSON 字段兼容。[CONCEPT: 后端测试] 证据:`server/tests/test_risk_observations_service.py` 覆盖 upsert、dashboard、feedback 状态流转。
|
||||
- [x] 后端算法测试:同类基线、降级口径、风险总分、图谱异常分。[CONCEPT: 算法与公式] 证据:`server/tests/test_financial_risk_graph_algorithm.py`,`pytest --ignore=.venv-ocr312 tests\test_financial_risk_graph_algorithm.py -q` 通过。
|
||||
- [x] 后端接口测试:单据风险观察、风险观察详情、风险看板聚合、工作记录关联风险观察。[CONCEPT: API 契约建议] 证据:`pytest --ignore=.venv-ocr312 tests\test_financial_risk_graph_algorithm.py tests\test_risk_observations_service.py -q` 本地与 Docker 均 7 passed。
|
||||
- [x] 前端测试:单据详情证据链展示和空状态。[CONCEPT: 前端测试] 证据:`node --test web/tests/risk-observation-evidence-card.test.mjs` 通过 3 项断言。
|
||||
- [x] 前端测试:员工技能详情不显示知识制度图谱。[CONCEPT: 回归测试] 证据:`web/tests/digital-employee-work-record-products.test.mjs` 断言员工技能详情不渲染 `KnowledgeIngestGraphView`。
|
||||
- [x] 前端测试:工作记录详情只展示对应任务的图谱和风险观察。[CONCEPT: 回归测试] 证据:`web/tests/digital-employee-work-record-products.test.mjs` 覆盖任务类型识别、产物类型解析和风险观察局部展开。
|
||||
- [x] 前端测试:风险看板筛选、趋势、排行和卡片数据一致。[CONCEPT: 前端测试] 证据:`node --test web/tests/risk-observation-dashboard.test.mjs` 通过 3 项断言,覆盖窗口筛选、趋势、排行和 KPI 数据源联动。
|
||||
- [x] 在 Docker 容器中执行后端定向测试,命令形态为 `docker exec x-financial-main sh -lc "cd /app && pytest <target> -q"`,测试超时控制在 60s 内。[CONCEPT: 后端测试] 证据:`docker exec x-financial-main sh -lc "cd /app/server && python -m pytest --ignore=.venv-ocr312 tests/test_financial_risk_graph_algorithm.py tests/test_risk_observations_service.py -q"` 已通过 7 passed。
|
||||
- [x] 执行前端构建验证,确认风险看板和详情变更不破坏现有页面。[CONCEPT: 前端测试] 证据:`npm.cmd run build` 已通过;`/app/overview` 与 `/api/v1/risk-observations/dashboard` 本地 HTTP 检查返回 200。
|
||||
|
||||
## 11. 验收
|
||||
|
||||
- [x] 单据详情能解释单个风险:结论、证据链、基线、制度条款、历史案例、建议动作齐全。[CONCEPT: 业务验收] 证据:`RiskObservationEvidenceCard.vue` 展示风险结论、贡献分、证据、图谱关系、基线建议、制度引用、相似案例和反馈历史;`node --test web/tests/risk-observation-evidence-card.test.mjs` 通过 3 项断言。
|
||||
- [x] 数字员工工作记录能解释一次任务:范围、数量、产出、失败、风险观察齐全。[CONCEPT: 业务验收] 证据:`DigitalEmployeeRunProducts.vue` 展示扫描范围、处理数量、任务产物、失败摘要和本次风险观察;`node --test web/tests/digital-employee-work-record-products.test.mjs` 通过 4 项断言。
|
||||
- [x] 风险看板能解释整体态势:总览、分布、趋势、排行、算法效果齐全。[CONCEPT: 业务验收] 证据:`RiskObservationDashboard.vue` 已包含总览 KPI、业务维度分布、趋势、信号排行、异常排行、算法闭环效果,`npm.cmd run build` 通过。
|
||||
- [x] 所有风险输出统一进入风险观察模型或兼容结构。[CONCEPT: 技术验收] 证据:Hermes 风险扫描已调用 `RiskObservationService.upsert_observation()`。
|
||||
- [x] 高风险观察至少包含两类证据来源。[CONCEPT: 算法验收] 证据:`_apply_evidence_source_gate()` 对单一证据源高风险封顶为 69,算法测试覆盖单源封顶和多源高风险通过。
|
||||
- [x] 风险观察保留算法版本、来源、时间、反馈状态。[CONCEPT: 技术验收] 证据:`RiskObservation` 模型包含 `algorithm_version`、`source`、`created_at`、`updated_at`、`feedback_status`。
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,78 +0,0 @@
|
||||
# 本体字段治理
|
||||
|
||||
## 背景
|
||||
|
||||
当前费用申请、报销助手、单据详情、风险规则和预算控制中存在字段口径不一致的问题。例如同一语义在不同环节被命名为 `transport_type`、`transport_mode`、`application_transport_mode`,或 `occurred_date`、`business_time`、`time_range`。这些字段如果不先进入本体层,会导致语义识别、规则判断、草稿保存和前端展示各自解释同一业务事实。
|
||||
|
||||
## 原则
|
||||
|
||||
所有业务字段必须先设计为本体字段,再下放到业务模块使用。
|
||||
|
||||
- 本体字段注册表是唯一字段源。
|
||||
- 业务层只允许消费本体 canonical 字段。
|
||||
- 非本体字段只能作为输入别名,必须在语义入口归一。
|
||||
- 页面控件字段、兼容字段、后端历史字段不能直接进入业务判断。
|
||||
- 新增业务字段时,必须先更新本体字段设计,再更新表单、助手上下文、持久化、风险规则和测试。
|
||||
|
||||
## 当前第一阶段范围
|
||||
|
||||
第一阶段先治理费用申请和报销链路:
|
||||
|
||||
- 个人工作台意图识别。
|
||||
- 费用申请预览和提交。
|
||||
- 报销助手快速发起报销。
|
||||
- 关联申请单生成报销草稿。
|
||||
- 报销详情智能录入和附件归集。
|
||||
- AI 预审、风险规则、审批流和预算流。
|
||||
|
||||
## 字段分层
|
||||
|
||||
本体 canonical 字段:
|
||||
|
||||
- `expense_type`
|
||||
- `time_range`
|
||||
- `location`
|
||||
- `reason`
|
||||
- `amount`
|
||||
- `transport_mode`
|
||||
- `attachments`
|
||||
- `customer_name`
|
||||
- `merchant_name`
|
||||
- `participants`
|
||||
- `application_claim_id`
|
||||
- `application_claim_no`
|
||||
- `application_days`
|
||||
- `application_date`
|
||||
- `application_lodging_daily_cap`
|
||||
- `application_subsidy_daily_cap`
|
||||
- `application_transport_policy`
|
||||
- `application_policy_estimate`
|
||||
|
||||
输入兼容别名:
|
||||
|
||||
- `transport_type`、`transportMode`、`application_transport_mode` -> `transport_mode`
|
||||
- `occurred_date`、`business_time`、`application_business_time` -> `time_range`
|
||||
- `business_location`、`application_location` -> `location`
|
||||
- `reason_value`、`business_reason`、`application_reason` -> `reason`
|
||||
- `attachment_names` -> `attachments`
|
||||
- `reimbursement_type`、`scene_label` -> `expense_type`
|
||||
|
||||
## 非合规判断
|
||||
|
||||
以下情况视为字段不合规:
|
||||
|
||||
- 新业务流程直接新增 `context_json` 字段但没有进入本体注册表。
|
||||
- 风险规则读取未注册字段。
|
||||
- 前端 `review_form_values` 输出页面控件字段。
|
||||
- 后端服务用别名字段做业务判断,而不是先归一成本体字段。
|
||||
- 同一业务事实在申请、报销、审批、预算中使用不同字段名。
|
||||
|
||||
## 验收口径
|
||||
|
||||
完成后应满足:
|
||||
|
||||
- 语义层能从上下文中生成统一本体实体。
|
||||
- 报销助手关联申请单后不再因为字段别名丢失追问出行方式。
|
||||
- `review_form_values` 对外输出本体字段,不输出页面别名字段。
|
||||
- 后端测试覆盖别名归一到本体字段。
|
||||
- 前端测试覆盖快速报销和核对抽屉只输出本体字段。
|
||||
@@ -1,40 +0,0 @@
|
||||
# 本体字段纠察记录
|
||||
|
||||
## 纠察口径
|
||||
|
||||
所有会参与意图识别、申请/报销草稿、费用明细、风险规则、审批或预算判断的字段,必须先进入本体字段注册表。
|
||||
|
||||
字段分为三类:
|
||||
|
||||
- 本体业务字段:可被业务逻辑、规则、页面表单直接消费。
|
||||
- 输入兼容别名:只允许在语义入口归一,不允许在业务判断中继续直接读取。
|
||||
- 上下文元数据:只表达会话、上传、编辑态、权限和执行链路,不作为业务事实。
|
||||
|
||||
## 已注册的业务字段
|
||||
|
||||
- 费用事实:`expense_type`、`time_range`、`location`、`reason`、`amount`、`transport_mode`、`attachments`
|
||||
- 对象事实:`customer_name`、`merchant_name`、`participants`
|
||||
- 员工事实:`employee_name`、`employee_no`、`department_name`、`employee_position`、`employee_grade`、`manager_name`
|
||||
- 预算事实:`budget_period`、`budget_subject`、`budget_amount`、`cost_center`、`warning_threshold`、`control_action`
|
||||
- 申请关联事实:`application_claim_id`、`application_claim_no`、`application_days`、`application_date`、`application_policy_estimate`
|
||||
|
||||
## 已登记为元数据的字段
|
||||
|
||||
- 会话与流程:`conversation_id`、`conversation_history`、`conversation_scenario`、`conversation_intent`、`session_type`、`entry_source`
|
||||
- 编辑与动作:`review_action`、`draft_claim_id`、`application_edit_mode`、`application_edit_claim_id`
|
||||
- 上传与 OCR:`attachment_count`、`attachment_names`、`ocr_documents`、`ocr_summary`、`review_document_form_values`
|
||||
- 客户端运行态:`client_now_iso`、`client_timezone_offset_minutes`
|
||||
- 权限与调试:`role_codes`、`is_admin`、`simulate_tool_failure`、`simulate_orchestrator_exception`
|
||||
|
||||
## 当前审计结论
|
||||
|
||||
- 未注册字段:已清零。
|
||||
- 历史别名直接读取:主要集中在员工上下文顶层字段,例如 `name`、`grade`、`department`、`position`。
|
||||
- 第一轮已把申请/报销关键链路的表单字段统一到 `expense_type`、`time_range`、`location`、`reason`、`amount`、`transport_mode`。
|
||||
|
||||
## 后续清理策略
|
||||
|
||||
1. 新增业务字段前,先更新 `ontology_field_registry.py`。
|
||||
2. 旧字段只作为输入别名保留,入口归一到 canonical 字段。
|
||||
3. 业务模块逐步停止直接读取旧别名。
|
||||
4. 使用 `server/scripts/audit_ontology_context_fields.py --strict` 作为收口质量闸。
|
||||
@@ -1,15 +0,0 @@
|
||||
# 本体字段治理 TODO
|
||||
|
||||
- [x] 建立本体字段注册表,集中维护 canonical 字段和输入别名。[CONCEPT: 字段分层]
|
||||
- [x] 在语义解析入口归一 `context_json.review_form_values`。[CONCEPT: 原则]
|
||||
- [x] 在本体实体抽取中把上下文字段桥接为 `transport_mode`、`reason`、`location` 等实体。[CONCEPT: 当前第一阶段范围]
|
||||
- [x] 报销助手 review 入口复用本体字段注册表,不再自己维护字段别名。[CONCEPT: 原则]
|
||||
- [x] 快速报销关联申请单上下文去除 `business_time`、`business_location`、`reason_value`、`reimbursement_type` 等非本体输出字段。[CONCEPT: 非合规判断]
|
||||
- [x] 核对抽屉提交上下文归一为本体字段。[CONCEPT: 验收口径]
|
||||
- [x] 补充本体层和前端字段归一回归测试。[CONCEPT: 验收口径]
|
||||
- [ ] 清查申请助手字段:`application_preview`、`application_fields`、`business_time_context` 是否都已归一本体。
|
||||
- [ ] 清查报销详情字段:智能录入、附件归集、费用明细、异常说明是否仍有非本体字段直传。
|
||||
- [ ] 清查风险规则字段:规则中心、Hermes 归一字段、OCR pipeline 字段是否有未注册业务字段。
|
||||
- [ ] 清查预算字段:预算控制、预算复核、预算操作上下文是否全部使用本体字段。
|
||||
- [ ] 清查审批字段:审批意见、退回原因、流程节点字段是否需要纳入本体或定义为流程元数据。
|
||||
- [ ] 增加字段合规扫描脚本,对新增 `review_form_values` / `context_json` 字段进行检查。
|
||||
@@ -1,266 +0,0 @@
|
||||
# 票据夹功能概念文档
|
||||
|
||||
更新时间:2026-05-29
|
||||
|
||||
## 功能一句话
|
||||
|
||||
票据夹用于归集用户已上传并经过 OCR 识别的原始票据文件,避免票据已识别但忘记关联报销单后无法找回。
|
||||
|
||||
## 背景与问题
|
||||
|
||||
当前系统有两条票据路径:
|
||||
|
||||
- 报销明细附件路径:票据上传到某个草稿费用明细后,会存入 `expense_claims` 附件目录,并写入附件元数据。
|
||||
- 独立 OCR 识别路径:报销对话里先上传票据识别时,`/ocr/recognize` 只返回识别结果,源文件使用临时目录,识别结束后会清理。
|
||||
|
||||
这会导致一个业务缺口:用户可能已经上传票据并完成 OCR,但还没有把票据关联到报销草稿。只要用户关闭会话、切走页面或忘记继续操作,原始票据就没有一个稳定入口可追溯。
|
||||
|
||||
票据夹要补齐这个缺口:凡是系统对用户上传文件做过 OCR 并持久化源文件,就应进入票据夹列表;后续用户可以查看、修正票据信息、删除无效票据,或一键把未关联票据带入报销对话。
|
||||
|
||||
## 目标与非目标
|
||||
|
||||
目标:
|
||||
|
||||
- 在左侧侧边栏的“单据中心”下面新增“票据夹”入口。
|
||||
- 建立票据源文件持久化能力,OCR 后保留原始文件、预览文件和识别元数据。
|
||||
- 提供票据夹列表,复用单据中心的紧凑列表视觉语言。
|
||||
- 支持“未关联票据 / 已关联票据”两个状态切换。
|
||||
- 支持票据详情:基本票据信息可编辑、原始文件可预览、底部返回列表和删除票据。
|
||||
- 支持“一键关联票据”:选择未关联票据,选择未提交草稿或新建报销单,再跳转到报销对话继续填写和关联。
|
||||
|
||||
非目标:
|
||||
|
||||
- 本轮不引入 `document_assets` 等数据库结构变更;先用文件资产和元数据 JSON 完成产品闭环。
|
||||
- 本轮不替换现有报销明细附件接口。
|
||||
- 本轮不把票据夹做成财务共享的全公司档案库;默认只展示当前登录用户自己的票据。
|
||||
- 本轮不在列表页直接完成报销单提交,提交仍回到现有对话核对流程。
|
||||
|
||||
## 用户与场景
|
||||
|
||||
涉及角色:
|
||||
|
||||
- 普通员工:上传票据后稍后再归集到草稿。
|
||||
- 经理或财务用户:在自己名下上传票据时同样需要留存和追溯。
|
||||
|
||||
典型场景:
|
||||
|
||||
1. 用户在个人工作台上传 3 张票据,OCR 成功后暂时没有保存草稿。
|
||||
2. 用户第二天打开票据夹,看到这 3 张票据仍在“未关联票据”。
|
||||
3. 用户进入详情,修正票据类型、金额或日期。
|
||||
4. 用户点击“一键关联票据”,多选未关联票据。
|
||||
5. 用户选择已有草稿,或选择新建报销单。
|
||||
6. 系统打开报销对话,把票据源文件和 OCR 信息带入现有核对流程。
|
||||
|
||||
## 功能能力
|
||||
|
||||
### 票据持久化
|
||||
|
||||
- OCR 入口接收文件后,在识别完成阶段保存源文件。
|
||||
- 保存位置建议为 `storage/receipt_folder/<owner_key>/<receipt_id>/`。
|
||||
- 每个票据目录包含:
|
||||
- 原始文件:`source.<ext>`
|
||||
- 预览文件:`preview.<ext>`,可为空
|
||||
- 元数据:`meta.json`
|
||||
- 元数据记录:
|
||||
- `id`
|
||||
- `owner_key`
|
||||
- `file_name`
|
||||
- `media_type`
|
||||
- `size_bytes`
|
||||
- `uploaded_at`
|
||||
- `status`: `unlinked` / `linked`
|
||||
- `linked_claim_id`
|
||||
- `linked_claim_no`
|
||||
- `linked_item_id`
|
||||
- `linked_at`
|
||||
- OCR 引擎、模型、文本、摘要、置信度、票据类型、场景、结构化字段、提示信息
|
||||
|
||||
### 列表
|
||||
|
||||
- 页签:
|
||||
- 未关联票据
|
||||
- 已关联票据
|
||||
- 表格字段建议:
|
||||
- 票据文件
|
||||
- 识别类型
|
||||
- 费用场景
|
||||
- 金额
|
||||
- 票据日期
|
||||
- OCR 置信度
|
||||
- 关联状态
|
||||
- 上传时间
|
||||
- 交互:
|
||||
- 搜索文件名、摘要、字段值、关联单号
|
||||
- 按状态切换
|
||||
- 点击行进入详情
|
||||
- 未关联页显示“一键关联票据”
|
||||
|
||||
### 详情
|
||||
|
||||
- 基本票据信息:
|
||||
- 文件名只读
|
||||
- 票据类型可编辑
|
||||
- 费用场景可编辑
|
||||
- 票据日期可编辑
|
||||
- 金额可编辑
|
||||
- 商户 / 出发地 / 到达地 / 票据号码等 OCR 字段可编辑
|
||||
- 原始文件展示:
|
||||
- 图片直接预览
|
||||
- PDF 用浏览器内嵌预览
|
||||
- 不可预览类型提供下载入口
|
||||
- 底部动作:
|
||||
- 返回列表
|
||||
- 删除票据
|
||||
|
||||
### 一键关联票据
|
||||
|
||||
流程:
|
||||
|
||||
1. 打开关联弹窗,展示未关联票据多选列表。
|
||||
2. 下一步展示当前用户未提交草稿报销单,也提供“新建报销单”选项。
|
||||
3. 确认后打开现有报销对话。
|
||||
4. 如果选择已有草稿:
|
||||
- 对话以 `link_to_existing_draft` 语义继续。
|
||||
- 携带 `draft_claim_id` 和票据文件。
|
||||
5. 如果选择新建报销单:
|
||||
- 对话以 `create_new_claim_from_documents` 语义继续。
|
||||
- 携带票据文件和 OCR 元数据。
|
||||
|
||||
## 方案设计
|
||||
|
||||
### 后端
|
||||
|
||||
新增模块:
|
||||
|
||||
- `schemas/receipt_folder.py`
|
||||
- `services/receipt_folder.py`
|
||||
- `api/v1/endpoints/receipt_folder.py`
|
||||
|
||||
接口建议:
|
||||
|
||||
- `GET /api/v1/receipt-folder?status=unlinked|linked|all`
|
||||
- `GET /api/v1/receipt-folder/{receipt_id}`
|
||||
- `PATCH /api/v1/receipt-folder/{receipt_id}`
|
||||
- `DELETE /api/v1/receipt-folder/{receipt_id}`
|
||||
- `GET /api/v1/receipt-folder/{receipt_id}/preview`
|
||||
- `GET /api/v1/receipt-folder/{receipt_id}/source`
|
||||
|
||||
OCR 改造:
|
||||
|
||||
- `/api/v1/ocr/recognize` 保持现有响应结构兼容。
|
||||
- 在识别后调用票据夹服务保存源文件和识别结果。
|
||||
- 给每个返回的 OCR 文档补充可选 `receipt_id`、`receipt_preview_url`、`receipt_source_url` 字段。
|
||||
|
||||
### 前端
|
||||
|
||||
新增模块:
|
||||
|
||||
- `services/receiptFolder.js`
|
||||
- `views/ReceiptFolderView.vue`
|
||||
- `assets/styles/views/receipt-folder-view.css`
|
||||
|
||||
导航改造:
|
||||
|
||||
- `useNavigation.js` 新增 `receiptFolder`,放在 `documents` 后面。
|
||||
- `accessControl.js` 将 `receiptFolder` 作为默认可见视图。
|
||||
- `router/index.js` 自动生成 `/app/receiptFolder` 路由。
|
||||
- `AppShellRouteView.vue` 渲染新页面,并允许页面触发 `openSmartEntry`。
|
||||
|
||||
对话衔接:
|
||||
|
||||
- 票据夹确认关联时,前端从 `source` 接口取回 Blob,构造 `File` 对象传给 `openSmartEntry`。
|
||||
- 同时把已编辑 OCR 元数据转为 `initialReceiptDocuments` 或直接通过 `prompt` / `extraContext` 进入对话。
|
||||
- 本轮优先用现有 `initial-files` 和 `initial-prompt` 打开对话,确保用户可以继续核对和保存。
|
||||
|
||||
## 算法与公式
|
||||
|
||||
当前功能不涉及显式数学公式。
|
||||
|
||||
列表排序使用上传时间倒序:
|
||||
|
||||
$$
|
||||
sortKey(receipt) = uploadedAt(receipt)
|
||||
$$
|
||||
|
||||
状态归类:
|
||||
|
||||
$$
|
||||
status(receipt) =
|
||||
\begin{cases}
|
||||
linked, & linkedClaimId \neq \emptyset \\
|
||||
unlinked, & linkedClaimId = \emptyset
|
||||
\end{cases}
|
||||
$$
|
||||
|
||||
## 测试方案
|
||||
|
||||
后端:
|
||||
|
||||
- OCR 识别后会保存源文件和 `meta.json`。
|
||||
- 列表只返回当前用户票据。
|
||||
- `status=unlinked` 只返回未关联票据。
|
||||
- 详情可读取 OCR 字段。
|
||||
- PATCH 后字段持久化。
|
||||
- 预览接口能返回图片或 PDF。
|
||||
- DELETE 只删除票据夹根目录下的目标票据。
|
||||
|
||||
前端:
|
||||
|
||||
- 导航中“票据夹”位于“单据中心”下面。
|
||||
- 列表空态、加载态、错误态可用。
|
||||
- 未关联和已关联两个页签计数正确。
|
||||
- 点击行进入详情。
|
||||
- 详情可保存字段、返回列表、删除票据。
|
||||
- 一键关联弹窗能完成票据选择和草稿选择。
|
||||
|
||||
集成:
|
||||
|
||||
- 上传票据触发 OCR 后,票据出现在票据夹。
|
||||
- 从票据夹选择未关联票据,可打开报销对话。
|
||||
- 选择已有草稿时,对话带入草稿单号。
|
||||
- 选择新建报销单时,对话提示基于票据新建。
|
||||
|
||||
## 指标与验收
|
||||
|
||||
- OCR 成功返回后,票据夹列表能查到对应源文件。
|
||||
- 票据源文件和预览文件在重启后仍可访问。
|
||||
- 未关联票据和已关联票据状态切换正确。
|
||||
- 票据详情字段修改后刷新仍保留。
|
||||
- 删除票据后列表不再显示,预览接口返回 404。
|
||||
- 侧边栏位置符合要求:票据夹在单据中心下面。
|
||||
- 单个新增核心前端和后端模块不超过 800 行。
|
||||
|
||||
## 风险与开放问题
|
||||
|
||||
- 当前报销草稿流主要持久化 OCR 文本和文件名,真实文件复制到报销明细附件目录仍需要进一步打通。
|
||||
- 本轮采用文件元数据而非数据库,适合先完成闭环;后续若需要审计、权限、跨用户协作和全文检索,应升级到资产表。
|
||||
- 已关联状态如何自动回写,需要在后续把票据夹 ID 与报销明细 `invoice_id` 建立更强绑定。
|
||||
- 多票据关联时,如果用户中途取消对话,本轮仍保留为未关联,避免误标。
|
||||
|
||||
## 2026-06-03 详情页与上传治理补充
|
||||
|
||||
本轮根据新的验收要求收敛为三块核心内容:
|
||||
|
||||
- 左侧为票据预览,使用共享详情页主区域承载,图片和 PDF 都以完整票据可见为优先目标,不再提供“打开源文件”按钮。
|
||||
- 右侧为识别票据详情,展示当前票据所有 OCR 字段和基础字段;用户点击“编辑”后可直接修改识别内容,保存后写回票据夹元数据。
|
||||
- 底部为关联信息;左侧预览卡底部同时展示用户编辑操作记录,用于后续财务判断人工修改痕迹。
|
||||
|
||||
编辑记录治理:
|
||||
|
||||
- `PATCH /receipt-folder/{receipt_id}` 在保存前后对可编辑票据信息做字段级 diff。
|
||||
- 每条编辑日志记录操作者、操作时间、字段名称、修改前值、修改后值。
|
||||
- 前端详情页只展示真实 `edit_logs`,不再用模拟操作日志替代。
|
||||
|
||||
重复上传治理:
|
||||
|
||||
- OCR 持久化票据时计算源文件 `sha256`。
|
||||
- 同一用户再次上传相同源文件时,不新建票据目录,返回已有 `receipt_id`,并在 OCR 文档 warnings 中提示“已上传过同样的单据,请不要重复上传。”
|
||||
|
||||
报销助手联动:
|
||||
|
||||
- 用户在报销助手上传新附件前,如果票据夹中存在未关联票据,先提示用户是否进入票据夹关联。
|
||||
- 用户可以选择“去票据夹关联”,也可以选择“继续上传新附件”;继续上传时只跳过本次未关联提醒,不影响后续重复附件校验。
|
||||
|
||||
删除级联:
|
||||
|
||||
- 已关联票据对应的报销单被删除时,票据夹中关联该报销单的票据源文件、预览文件和元数据一并删除。
|
||||
@@ -1,114 +0,0 @@
|
||||
# 票据夹功能 TODO
|
||||
|
||||
更新时间:2026-05-29
|
||||
|
||||
## 阶段一:调研与契约
|
||||
|
||||
- [x] 梳理现有单据中心导航、列表样式和详情入口。[CONCEPT: 方案设计]
|
||||
证据:已确认 `DocumentsCenterView.vue`、`useNavigation.js`、`AppShellRouteView.vue` 是前端入口。
|
||||
|
||||
- [x] 梳理现有 OCR 和报销附件存储链路。[CONCEPT: 背景与问题]
|
||||
证据:已确认 `/ocr/recognize` 只临时识别;报销明细附件由 `expense_claim_attachment_*` 写入 `expense_claims` 存储。
|
||||
|
||||
- [x] 确定本轮不做数据库结构变更,先用票据文件资产和元数据 JSON 完成闭环。[CONCEPT: 目标与非目标]
|
||||
证据:避免新增迁移,降低本轮开发风险。
|
||||
|
||||
## 阶段二:文档
|
||||
|
||||
- [x] 创建 `document/development/receipt-folder/CONCEPT.md`。[CONCEPT: 全文]
|
||||
证据:本文档已落地。
|
||||
|
||||
- [x] 创建 `document/development/receipt-folder/TODO.md`。[CONCEPT: 测试方案]
|
||||
证据:本文档已落地。
|
||||
|
||||
## 阶段三:后端票据资产层
|
||||
|
||||
- [x] 新增 `schemas/receipt_folder.py`,定义列表项、详情、字段更新和删除响应。[CONCEPT: 后端]
|
||||
证据:已新增 `ReceiptFolderItemRead`、`ReceiptFolderDetailRead`、`ReceiptFolderUpdate`、`ReceiptFolderDeleteResponse`。
|
||||
|
||||
- [x] 新增 `services/receipt_folder.py`,负责源文件保存、元数据读写、预览解析、列表过滤和安全路径校验。[CONCEPT: 票据持久化]
|
||||
证据:`ReceiptFolderService` 已覆盖 OCR 批量持久化、已关联附件同步、详情更新、预览/源文件解析与目录安全校验。
|
||||
|
||||
- [x] 新增 `api/v1/endpoints/receipt_folder.py`,暴露列表、详情、更新、删除、预览和源文件接口。[CONCEPT: 后端]
|
||||
证据:已提供 `GET/PATCH/DELETE /receipt-folder/{receipt_id}` 及 `preview/source` 文件接口。
|
||||
|
||||
- [x] 在 `api/v1/router.py` 注册票据夹接口。[CONCEPT: 后端]
|
||||
证据:已 include `receipt_folder_router`。
|
||||
|
||||
- [x] 改造 `/ocr/recognize`,OCR 后保存源文件并把 `receipt_id` 等可选字段带回前端。[CONCEPT: OCR 改造]
|
||||
证据:`OcrRecognizeDocumentRead` 已补充 `receipt_id`、`receipt_status`、`receipt_preview_url`、`receipt_source_url`;来源于票据夹的 `receipt_ids` 会复用原票据,避免重复入库。
|
||||
|
||||
## 阶段四:前端票据夹页面
|
||||
|
||||
- [x] 新增 `services/receiptFolder.js`,封装票据夹接口和 Blob 文件读取。[CONCEPT: 前端]
|
||||
证据:已封装列表、详情、更新、删除、文件读取和 `buildReceiptFile`。
|
||||
|
||||
- [x] 新增 `ReceiptFolderView.vue`,实现列表、状态页签、搜索、一键关联入口和详情切换。[CONCEPT: 列表]
|
||||
证据:页面已包含未关联/已关联页签、搜索、表格、详情、编辑、预览和删除动作。
|
||||
|
||||
- [x] 新增 `receipt-folder-view.css`,复用单据中心紧凑企业级视觉,避免继续拉大现有 `DocumentsCenterView.vue`。[CONCEPT: 列表]
|
||||
证据:票据夹样式独立落在 `assets/styles/views/receipt-folder-view.css`,核心文件均未超过 800 行。
|
||||
|
||||
- [x] 在 `useNavigation.js` 增加 `receiptFolder`,并放在 `documents` 后面。[CONCEPT: 前端]
|
||||
证据:`appViews` 与 `navItems` 中 `receiptFolder` 均紧跟 `documents`。
|
||||
|
||||
- [x] 在 `accessControl.js` 增加默认可见权限和默认路由顺序。[CONCEPT: 前端]
|
||||
证据:已加入 `DEFAULT_APP_VIEW_ORDER` 与 `ALWAYS_VISIBLE_VIEWS`。
|
||||
|
||||
- [x] 在 `AppShellRouteView.vue` 渲染票据夹页面,并让页面可打开报销对话。[CONCEPT: 一键关联票据]
|
||||
证据:已接入 `ReceiptFolderView` 并转发 `open-assistant` 到 `openSmartEntry`。
|
||||
|
||||
## 阶段五:一键关联流程
|
||||
|
||||
- [x] 实现未关联票据多选弹窗第一步。[CONCEPT: 一键关联票据]
|
||||
证据:`ElDialog` 第一阶段使用 `ElCheckboxGroup` 多选未关联票据。
|
||||
|
||||
- [x] 实现未提交草稿选择和“新建报销单”选择第二步。[CONCEPT: 一键关联票据]
|
||||
证据:第二阶段读取草稿报销单,并保留 `新建报销单` 选项。
|
||||
|
||||
- [x] 从票据源文件接口取回 Blob 并构造 `File` 对象传给报销对话。[CONCEPT: 对话衔接]
|
||||
证据:`buildReceiptFile` 从 `source_url` 读取 Blob 并生成带 `receiptId` 的 `File`。
|
||||
|
||||
- [x] 选择已有草稿时,打开对话并带入草稿单号和关联提示。[CONCEPT: 一键关联票据]
|
||||
证据:选择草稿后以 `source: 'detail'`、`request` 和关联 prompt 打开报销对话;附件上传会携带 `receipt_id` 并回写原票据为已关联。
|
||||
|
||||
- [x] 选择新建报销单时,打开对话并带入基于票据新建的提示。[CONCEPT: 一键关联票据]
|
||||
证据:新建路径以 `source: 'receipt-folder'` 携带票据文件和新建 prompt。
|
||||
|
||||
## 阶段六:测试与验证
|
||||
|
||||
- [x] 补充后端票据夹服务和接口测试,超时时间控制在 60s 内。[CONCEPT: 测试方案]
|
||||
证据:`docker exec x-financial-main ... pytest server/tests/test_ocr_endpoints.py server/tests/test_reimbursement_endpoints.py -q` 通过,8 passed,耗时 11.41s。
|
||||
|
||||
- [x] 补充前端导航和票据夹视图模型测试。[CONCEPT: 测试方案]
|
||||
证据:`navigation-route-resolution.test.mjs` 已覆盖路由顺序,新增 `receipt-folder-view.test.mjs` 覆盖视图关键能力。
|
||||
|
||||
- [x] 运行前端构建或定向测试。[CONCEPT: 指标与验收]
|
||||
证据:`node web/tests/navigation-route-resolution.test.mjs`、`node web/tests/receipt-folder-view.test.mjs`、`npm.cmd run build` 均通过。
|
||||
|
||||
- [x] 在 Docker `x-financial-main` 的 `/app` 内运行后端定向测试。[CONCEPT: 测试方案]
|
||||
证据:`pytest server/tests/test_ocr_endpoints.py server/tests/test_reimbursement_endpoints.py -q` 在容器内通过,8 passed。
|
||||
|
||||
- [x] 手动核对侧边栏位置、列表密度、详情预览和关联弹窗。[CONCEPT: 指标与验收]
|
||||
证据:内置浏览器打开 `http://localhost:5173/app/receiptFolder`,侧边栏中“票据夹”位于“单据中心”下方,未关联/已关联页签与空态渲染正常,控制台无相关错误。
|
||||
|
||||
## 阶段七:收口
|
||||
|
||||
- [x] 回看 `CONCEPT.md` 验收标准,确认已实现项均有证据。[CONCEPT: 指标与验收]
|
||||
证据:OCR 持久化、列表/详情/预览、侧边栏位置、一键关联入口和文件行数约束均已覆盖。
|
||||
|
||||
- [x] 更新本 TODO 的完成状态和验证记录。[CONCEPT: 测试方案]
|
||||
证据:本文件已补充完成勾选和验证命令记录。
|
||||
|
||||
## 阶段八:2026-06-03 详情页与上传治理收口
|
||||
|
||||
- [x] 将票据夹详情页收敛为共享详情布局下的三块内容:左侧完整预览、右侧识别票据详情、底部关联信息。[CONCEPT: 2026-06-03 详情页与上传治理补充]
|
||||
证据:`node web/tests/receipt-folder-view.test.mjs`、`npm.cmd run build`、容器内 `cd /app/web && npm run build` 均通过。
|
||||
- [x] 支持识别票据详情编辑,并在后端保存字段级编辑日志。[CONCEPT: 编辑记录治理]
|
||||
证据:容器内 `pytest -q server/tests/test_ocr_endpoints.py server/tests/test_receipt_folder_service.py` 通过,3 passed。
|
||||
- [x] OCR 持久化时识别同一用户重复上传的相同源文件,返回已有票据并提示不要重复上传。[CONCEPT: 重复上传治理]
|
||||
证据:`test_ocr_endpoints.py` 已覆盖重复上传返回原 `receipt_id` 和 warnings。
|
||||
- [x] 报销助手上传附件前提示票据夹中存在未关联票据,并提供进入票据夹关联或继续上传的建议动作。[CONCEPT: 报销助手联动]
|
||||
证据:`receipt-folder-view.test.mjs` 覆盖 `fetchReceiptFolderItems('unlinked')`、`open_receipt_folder` 和 `continue_upload_with_unlinked_receipts`。
|
||||
- [x] 删除已关联报销单时,同步删除票据夹中关联该报销单的票据文件。[CONCEPT: 删除级联]
|
||||
证据:`test_receipt_folder_service.py` 已覆盖 `delete_receipts_for_claim` 删除后不可再读取票据。
|
||||
@@ -1,581 +0,0 @@
|
||||
# 风险规则可解释流程判断改造方案
|
||||
|
||||
## 功能一句话
|
||||
|
||||
把风险规则从“自然语言生成一段 JSON”升级为“自然语言、字段本体、可执行 JSON DSL、流程判断图、测试命中路径、版本修改”一致闭环,让业务用户能看懂规则怎么判断,让系统按同一套逻辑执行。
|
||||
|
||||
## 背景与问题
|
||||
|
||||
当前风险规则已经具备自然语言创建、JSON 风险规则、风险评分、详情页流程图、仿真测试和上线启用能力,但仍有几个关键缺口:
|
||||
|
||||
- 规则解释不够稳定。用户输入复杂业务规则后,系统可能把“城市是否一致、日期是否越界、是否存在合理说明”解释成“是否出现关键词”,这会导致业务语义失真。
|
||||
- 流程图容易变成展示装饰。如果流程图不严格从可执行 JSON DSL 派生,就会出现“页面看起来是 A,后端实际执行是 B”的问题。
|
||||
- 测试结果缺少路径解释。用户上传票据和输入意图后,需要知道系统识别到了哪些字段、走过哪些判断节点、为什么命中或未命中。
|
||||
- 规则修改缺少版本化闭环。已上线规则不能直接覆盖,应创建修订版本,旧版本继续生效,新版本测试通过后再替换。
|
||||
- 常见费控规则需要模板化扩展。预算、票据、差旅、招待、采购/AP、企业卡等规则应进入规则模板库,但仍必须走同一套解释和执行链路。
|
||||
|
||||
## 目标与非目标
|
||||
|
||||
### 目标
|
||||
|
||||
- [G1] 自然语言规则经过 Hermes 语义理解后,生成结构稳定、可校验、可执行的 JSON DSL。
|
||||
- [G2] 流程判断图必须由 JSON DSL 派生,不能直接由自然语言单独生成。
|
||||
- [G3] 详情页展示“文字流程解释 + 流程图 + 使用字段 + 风险分数 + 规则状态”,让业务用户能确认系统理解是否正确。
|
||||
- [G4] 测试规则时展示本次样例或票据仿真的字段识别结果、判断路径、命中节点和最终结论。
|
||||
- [G5] 用户觉得规则不合理时,通过“创建修订版本”修改,线上版本保持稳定。
|
||||
- [G6] 常见费控规则模板库可以复用同一套 DSL、流程图和测试机制。
|
||||
|
||||
### 非目标
|
||||
|
||||
- [NG1] 本期不做流程图编辑器,不允许拖拽、改节点、缩放编辑或在线画图。
|
||||
- [NG2] 本期不让大模型作为风险命中裁判。Hermes 只负责理解、生成、解释和辅助解析,最终命中由规则执行器决定。
|
||||
- [NG3] 本期不把所有复杂政策一次性建成完整专家系统,先保证规则表达、解释和执行一致。
|
||||
- [NG4] 本期不直接覆盖已上线规则,所有线上修改都走修订版本。
|
||||
|
||||
## 用户与场景
|
||||
|
||||
- 财务规则制定者:创建风险规则,查看系统理解是否正确,修改草稿规则。
|
||||
- 高级财务人员 / admin:审核、上线、下线、启用、停用、删除未发布规则。
|
||||
- 普通报销用户:在真实业务命中风险时看到简明原因,可反馈误判或漏判。
|
||||
- 系统执行链路:报销、费用申请、预算占用、票据识别、采购/AP 等场景只加载已上线且已启用的规则。
|
||||
|
||||
核心场景:
|
||||
|
||||
1. 新建规则:输入规则标题、费用业务环节、费用领域、是否需要附件、自然语言规则。
|
||||
2. 生成规则:Hermes 结合字段本体输出 JSON DSL、业务说明、风险评分、流程模型和 SVG。
|
||||
3. 查看详情:用户确认“系统理解的字段、判断条件、例外说明、命中动作”是否正确。
|
||||
4. 仿真测试:用户上传附件并输入测试意图,系统统一识别字段,再由执行器判断当前规则。
|
||||
5. 修改规则:未上线规则直接编辑;已上线规则创建修订版本,测试通过后发布替换。
|
||||
|
||||
## 功能能力
|
||||
|
||||
### C1. 自然语言输入能力
|
||||
|
||||
新建风险规则表单应包含:
|
||||
|
||||
- 规则标题。
|
||||
- 业务环节:费用申请、报销、预算控制、付款/采购等。
|
||||
- 费用领域:差旅、住宿、交通、招待、办公、培训、会议、软件服务、通讯、福利、预算、发票、采购/AP、通用。
|
||||
- 是否需要附件:需要时测试弹窗开放附件上传;不需要时隐藏上传入口。
|
||||
- 自然语言规则描述。
|
||||
|
||||
风险等级不允许用户手动选择,由评分模型输出风险分数和等级。
|
||||
|
||||
### C2. 语义理解与字段本体映射
|
||||
|
||||
Hermes 需要输出一份中间语义计划,而不是直接写死 JSON:
|
||||
|
||||
- 规则意图:判断什么业务风险。
|
||||
- 适用范围:业务环节、费用领域、费用科目、单据类型。
|
||||
- 所需字段:中文解释、英文字段名、来源、是否必填。
|
||||
- 票据字段:OCR 或文档智能识别得到的城市、日期、金额、销售方、发票号等。
|
||||
- 判断步骤:按顺序表达条件、分支、例外说明和命中动作。
|
||||
- 例外条件:例如延期、改签、跨城办事、临时任务等说明。
|
||||
- 风险动作:提醒、人工复核、要求补充说明、退回修改、禁止提交。
|
||||
|
||||
字段展示统一为:
|
||||
|
||||
```text
|
||||
申报目的地[claim.destination_city]
|
||||
明细发生地点[item.location_city]
|
||||
交通票行程城市[receipt.transport_route_cities]
|
||||
住宿发票城市[receipt.hotel_city]
|
||||
出差开始日期[trip.start_date]
|
||||
出差结束日期[trip.end_date]
|
||||
报销事由[claim.reason]
|
||||
```
|
||||
|
||||
### C3. 可执行 JSON DSL
|
||||
|
||||
JSON DSL 应表达规则执行逻辑,而不是保存自然语言摘要。建议基本结构:
|
||||
|
||||
```json
|
||||
{
|
||||
"rule_id": "risk.travel.city_mismatch",
|
||||
"version": "v1",
|
||||
"scope": {
|
||||
"business_stage": "reimbursement",
|
||||
"expense_types": ["travel", "lodging"]
|
||||
},
|
||||
"required_fields": [
|
||||
{
|
||||
"label": "申报目的地",
|
||||
"field": "claim.destination_city",
|
||||
"source": "claim",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"conditions": {
|
||||
"all": [
|
||||
{
|
||||
"op": "in_scope",
|
||||
"field": "expense.type",
|
||||
"values": ["travel", "lodging"]
|
||||
},
|
||||
{
|
||||
"op": "any_present",
|
||||
"fields": [
|
||||
"receipt.transport_route_cities",
|
||||
"receipt.hotel_city",
|
||||
"item.location_city"
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "none_match",
|
||||
"left_fields": [
|
||||
"receipt.transport_route_cities",
|
||||
"receipt.hotel_city"
|
||||
],
|
||||
"right_fields": [
|
||||
"claim.destination_city",
|
||||
"item.location_city",
|
||||
"trip.route_cities"
|
||||
],
|
||||
"matcher": "city_equivalent"
|
||||
},
|
||||
{
|
||||
"op": "not_contains_any",
|
||||
"field": "claim.reason",
|
||||
"values": ["延期", "改签", "跨城办事", "临时任务", "绕行"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"action": {
|
||||
"risk_level": "high",
|
||||
"risk_score": 76,
|
||||
"decision": "review_required",
|
||||
"message": "票据城市与申报目的地或行程城市不一致,且未说明合理原因。"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
核心要求:
|
||||
|
||||
- 城市、日期、金额、人员、供应商等字段必须使用专门比较算子,不能退化成“关键词出现”。
|
||||
- 复杂规则允许多层条件组合:`all`、`any`、`not`、`branch`、`exists`、`range`、`compare`、`semantic_contains`。
|
||||
- 例外说明可以使用语义包含,但只能影响“是否进入复核/降级/豁免”,不能替代结构化字段判断。
|
||||
- DSL 生成后必须通过 schema 校验和执行器 dry-run。
|
||||
|
||||
### C4. 流程判断图
|
||||
|
||||
流程图不是编辑器,也不是自然语言插图。流程图必须由 JSON DSL 转换成 `flow_model`,再生成 SVG。
|
||||
|
||||
建议 `flow_model`:
|
||||
|
||||
```json
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "start",
|
||||
"type": "start",
|
||||
"title": "开始",
|
||||
"description": "进入差旅住宿报销风险检查"
|
||||
},
|
||||
{
|
||||
"id": "scope",
|
||||
"type": "decision",
|
||||
"title": "是否属于差旅住宿报销",
|
||||
"fields": ["expense.type", "claim.business_stage"]
|
||||
},
|
||||
{
|
||||
"id": "city_match",
|
||||
"type": "decision",
|
||||
"title": "票据城市是否匹配申报或行程城市",
|
||||
"fields": [
|
||||
"receipt.hotel_city",
|
||||
"receipt.transport_route_cities",
|
||||
"claim.destination_city",
|
||||
"trip.route_cities"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "hit",
|
||||
"type": "risk",
|
||||
"title": "命中高风险",
|
||||
"description": "要求补充行程说明或退回修改"
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{ "from": "start", "to": "scope", "label": "开始检查" },
|
||||
{ "from": "scope", "to": "end_pass", "label": "否" },
|
||||
{ "from": "scope", "to": "city_match", "label": "是" },
|
||||
{ "from": "city_match", "to": "end_pass", "label": "是" },
|
||||
{ "from": "city_match", "to": "hit", "label": "否" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
流程图展示要求:
|
||||
|
||||
- 详情页左侧为文字流程解释,右侧为“流程图”。
|
||||
- 判断分支用“是 / 否 / 通过 / 不通过 / 缺失 / 存在”等明确标签。
|
||||
- 风险命中框使用风险等级颜色:低风险蓝色,中风险橙色,高风险红色,极高风险深红色。
|
||||
- 普通节点保持 SaaS 白底、细边框、低饱和样式,不能整张图都染成风险色。
|
||||
- 图只做展示,不响应拖拽、编辑、缩放和节点点击。
|
||||
- 节点数量超过 8 个时,需要自动压缩文字、合并说明节点或分页展示,避免图过大。
|
||||
|
||||
### C5. 测试命中路径
|
||||
|
||||
测试规则弹窗应展示三类信息:
|
||||
|
||||
1. 输入与识别结果
|
||||
- 用户自然语言测试意图。
|
||||
- 上传附件清单。
|
||||
- OCR / 文档智能识别字段。
|
||||
- Hermes 辅助规范化后的结构化字段。
|
||||
|
||||
2. 规则执行结果
|
||||
- 是否进入适用范围。
|
||||
- 每个判断节点的输入值、比较方式、判断结果。
|
||||
- 命中的风险动作。
|
||||
- 未命中的原因。
|
||||
|
||||
3. 流程图路径高亮
|
||||
- 使用同一个 `flow_model`。
|
||||
- 本次执行走过的节点和边由执行器输出 `trace`。
|
||||
- 前端按 `trace` 高亮路径。
|
||||
|
||||
执行 trace 示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"trace_id": "run_001",
|
||||
"matched": true,
|
||||
"risk_level": "high",
|
||||
"risk_score": 76,
|
||||
"steps": [
|
||||
{
|
||||
"node_id": "scope",
|
||||
"result": true,
|
||||
"inputs": {
|
||||
"expense.type": "住宿费",
|
||||
"claim.business_stage": "reimbursement"
|
||||
}
|
||||
},
|
||||
{
|
||||
"node_id": "city_match",
|
||||
"result": false,
|
||||
"inputs": {
|
||||
"receipt.hotel_city": "北京",
|
||||
"claim.destination_city": "上海",
|
||||
"trip.route_cities": ["武汉", "上海"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### C6. 规则修改与版本化
|
||||
|
||||
规则修改分三种:
|
||||
|
||||
- 未上线规则:允许创建者或 admin 直接编辑,保存后重新生成 DSL、流程图、评分和说明。
|
||||
- 已上线规则:不允许直接覆盖,必须点击“创建修订版本”。
|
||||
- 业务用户反馈:只能提交误判/漏判反馈,由管理员决定是否创建修订版本。
|
||||
|
||||
已上线规则修改流程:
|
||||
|
||||
```text
|
||||
线上版本 active
|
||||
↓
|
||||
创建修订版本 draft_revision
|
||||
↓
|
||||
编辑自然语言 / 参数 / 附件要求
|
||||
↓
|
||||
重新生成 JSON DSL + 流程图 + 评分
|
||||
↓
|
||||
仿真测试通过
|
||||
↓
|
||||
发布新版本
|
||||
↓
|
||||
旧版本归档,新版本 active
|
||||
```
|
||||
|
||||
版本记录必须包含:
|
||||
|
||||
- 修改人。
|
||||
- 修改原因。
|
||||
- 修改前后自然语言差异。
|
||||
- 修改前后 DSL 差异。
|
||||
- 测试报告。
|
||||
- 发布时间。
|
||||
- 是否替换线上版本。
|
||||
|
||||
## 方案设计
|
||||
|
||||
### 总体链路
|
||||
|
||||
```text
|
||||
自然语言规则
|
||||
↓
|
||||
字段本体召回与业务域约束
|
||||
↓
|
||||
Hermes 生成语义计划 semantic_plan
|
||||
↓
|
||||
语义计划校验与补全
|
||||
↓
|
||||
生成 JSON DSL
|
||||
↓
|
||||
Schema 校验 + 执行器 dry-run
|
||||
↓
|
||||
风险评分 risk_score / risk_level
|
||||
↓
|
||||
DSL 转 flow_model
|
||||
↓
|
||||
flow_model 转 flow_diagram_svg
|
||||
↓
|
||||
详情展示 + 仿真测试 + 上线执行
|
||||
```
|
||||
|
||||
### 前端设计
|
||||
|
||||
涉及入口:
|
||||
|
||||
- `AuditRuleDialogs.vue`:新建风险规则表单,后续增加修订版本编辑入口。
|
||||
- `AuditJsonRiskRuleDetail.vue`:详情页展示基本信息、测试状态、流程解释、流程图、操作按钮。
|
||||
- `RiskRuleFlowDiagram.vue`:只负责展示 SVG 或由 `flow_model` 派生的静态图,不做编辑。
|
||||
- `RiskRuleTestDialog.vue`:仿真测试窗口,展示输入识别、执行路径、测试报告。
|
||||
- `auditViewRiskRuleModel.js` / `auditViewModel.js`:规则详情视图模型、列表字段和状态映射。
|
||||
|
||||
详情页建议结构:
|
||||
|
||||
```text
|
||||
Topbar:规则标题、状态、风险分数、风险等级、上线/启用状态
|
||||
|
||||
基本信息:费用领域、业务环节、附件要求、创建人、上线时间、最后操作、测试状态
|
||||
|
||||
判断流程:
|
||||
左侧:文字流程解释
|
||||
右侧:流程图
|
||||
|
||||
测试与版本:
|
||||
最近测试报告
|
||||
修订版本 / 历史版本
|
||||
操作按钮
|
||||
```
|
||||
|
||||
修改规则界面建议采用左右布局:
|
||||
|
||||
- 左侧:自然语言规则编辑、规则标题、费用领域、附件要求。
|
||||
- 右侧:系统解释预览,包括字段、本体映射、流程解释、风险分数。
|
||||
- 底部:重新生成、保存草稿、测试规则、提交上线。
|
||||
|
||||
### 后端设计
|
||||
|
||||
已有相关模块应优先复用:
|
||||
|
||||
- `risk_rule_generation.py`:规则生成主流程。
|
||||
- `risk_rule_generation_prompt.py`:Hermes 提示词。
|
||||
- `risk_rule_generation_ontology.py`:字段本体和费用领域约束。
|
||||
- `risk_rule_generation_semantics.py`:自然语言语义解释。
|
||||
- `risk_rule_generation_interpreter.py`:解释结果到 DSL。
|
||||
- `risk_rule_scoring.py`:风险评分。
|
||||
- `risk_rule_flow_diagram.py`:流程图 SVG 生成。
|
||||
- `risk_rule_manifest_normalizer.py`:规则 manifest 规范化。
|
||||
- `risk_rule_template_executor.py`:规则执行器。
|
||||
- `agent_asset_risk_rule_testing.py`:规则测试、删除、发布、启用。
|
||||
- `agent_asset_risk_rule_simulation.py`:仿真测试对话。
|
||||
|
||||
后端需要补齐的能力:
|
||||
|
||||
- 生成 `semantic_plan` 并持久化到 `config_json` 或版本内容中。
|
||||
- 生成并持久化 `flow_model`,再生成 `flow_diagram_svg`。
|
||||
- 执行器输出 `trace`,用于测试解释和流程图高亮。
|
||||
- 支持创建修订版本,避免直接覆盖 active 版本。
|
||||
- 支持从常见模板创建规则,模板也走同一套生成和校验链路。
|
||||
|
||||
### 接口设计
|
||||
|
||||
建议新增或调整:
|
||||
|
||||
```text
|
||||
POST /agent-assets/risk-rules/generate
|
||||
根据自然语言创建生成任务,返回生成中资产。
|
||||
|
||||
POST /agent-assets/{asset_id}/risk-rules/regenerate
|
||||
对草稿或修订版本重新生成 DSL、评分和流程图。
|
||||
|
||||
POST /agent-assets/{asset_id}/risk-rules/revisions
|
||||
基于已上线规则创建修订版本。
|
||||
|
||||
PATCH /agent-assets/{asset_id}/risk-rules/draft
|
||||
保存未上线规则或修订版本的编辑内容。
|
||||
|
||||
POST /agent-assets/{asset_id}/risk-rule-tests/simulate
|
||||
独立仿真测试,返回字段识别、执行结果、trace。
|
||||
|
||||
GET /agent-assets/{asset_id}/risk-rule-tests/latest
|
||||
返回最近测试摘要。
|
||||
|
||||
POST /agent-assets/{asset_id}/publish
|
||||
发布通过测试的规则版本。
|
||||
```
|
||||
|
||||
### 数据设计
|
||||
|
||||
建议在风险规则版本内容或 `config_json` 中保留:
|
||||
|
||||
```json
|
||||
{
|
||||
"source_text": "用户输入的自然语言规则",
|
||||
"semantic_plan": {},
|
||||
"dsl": {},
|
||||
"flow_model": {},
|
||||
"flow_diagram_svg": "<svg>...</svg>",
|
||||
"flow_explanation": [],
|
||||
"risk_score": 76,
|
||||
"risk_level": "high",
|
||||
"required_attachment": true,
|
||||
"required_fields": [],
|
||||
"last_operation": {
|
||||
"action": "publish",
|
||||
"actor": "admin",
|
||||
"at": "2026-05-30T10:00:00+08:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
测试记录保留:
|
||||
|
||||
```json
|
||||
{
|
||||
"test_type": "simulation",
|
||||
"input_text": "我去北京出差 3 天,上传武汉到上海车票",
|
||||
"attachments": [],
|
||||
"recognized_fields": {},
|
||||
"normalized_fields": {},
|
||||
"execution_result": {},
|
||||
"trace": {},
|
||||
"passed": true,
|
||||
"tester": "admin",
|
||||
"tested_at": "2026-05-30T10:10:00+08:00"
|
||||
}
|
||||
```
|
||||
|
||||
## 算法与公式
|
||||
|
||||
### 风险评分
|
||||
|
||||
风险评分由模型辅助判断,但必须结构化输出。建议使用可解释加权模型:
|
||||
|
||||
$$
|
||||
score = \min(100, base + \sum_{i=1}^{n} w_i x_i + c + e)
|
||||
$$
|
||||
|
||||
变量说明:
|
||||
|
||||
- $base$:业务领域基础风险分。预算、发票、付款类通常高于普通提示类。
|
||||
- $x_i$:风险因子是否存在或强度,例如金额影响、附件缺失、字段冲突、越权、历史重复。
|
||||
- $w_i$:风险因子权重。
|
||||
- $c$:复杂度修正,例如多字段交叉、跨单据、跨时间窗口、跨附件识别。
|
||||
- $e$:例外说明修正。存在合理说明时可降低,但不能直接清零。
|
||||
|
||||
等级映射:
|
||||
|
||||
- 0-30:低风险。
|
||||
- 31-60:中风险。
|
||||
- 61-80:高风险。
|
||||
- 81-100:极高风险。
|
||||
|
||||
### 流程复杂度控制
|
||||
|
||||
为了避免流程图过大,建议定义流程复杂度:
|
||||
|
||||
$$
|
||||
complexity = node_count + 0.5 \times edge_count + branch_count
|
||||
$$
|
||||
|
||||
处理规则:
|
||||
|
||||
- `complexity <= 12`:单图展示。
|
||||
- `12 < complexity <= 20`:合并说明节点,保留关键判断。
|
||||
- `complexity > 20`:详情页展示主流程,测试弹窗展示完整 trace。
|
||||
|
||||
## 测试方案
|
||||
|
||||
### 单元测试
|
||||
|
||||
- 语义计划生成:复杂差旅城市规则不能退化为关键词判断。
|
||||
- DSL schema 校验:缺字段、非法算子、空 action 必须失败。
|
||||
- 执行器:城市匹配、日期范围、金额阈值、附件缺失、例外说明。
|
||||
- 流程转换:同一 DSL 生成稳定的 `flow_model` 和 SVG。
|
||||
- 风险评分:低/中/高/极高边界分数。
|
||||
|
||||
### 接口测试
|
||||
|
||||
- 新建规则返回生成中资产。
|
||||
- 生成完成后包含 `dsl`、`flow_model`、`flow_diagram_svg`、`risk_score`。
|
||||
- 仿真测试返回 `recognized_fields`、`normalized_fields`、`trace`。
|
||||
- 未测试通过的规则不能发布。
|
||||
- 已上线规则创建修订版本,不覆盖线上版本。
|
||||
|
||||
### 前端测试
|
||||
|
||||
- 新建弹窗不再选择风险等级。
|
||||
- 详情页展示风险分数、流程解释、流程图。
|
||||
- 流程图不可点击、不可拖拽、无工具栏。
|
||||
- 测试弹窗显示字段识别结果和判断路径。
|
||||
- 已上线规则只能创建修订版本修改。
|
||||
|
||||
### 容器验证
|
||||
|
||||
后续开发验证默认在 Docker 容器内执行:
|
||||
|
||||
```bash
|
||||
docker exec x-financial-main sh -lc "cd /app/server && pytest <target> --timeout=60"
|
||||
docker exec x-financial-main sh -lc "cd /app/web && npm run build"
|
||||
```
|
||||
|
||||
### 本轮落地结果
|
||||
|
||||
已落地接口:
|
||||
|
||||
- `GET /agent-assets/risk-rules/templates`:返回预算、票据、差旅、招待、采购/AP、企业卡、通用模板分组,包含默认自然语言、字段清单、附件要求和 DSL 样例。
|
||||
- `PATCH /agent-assets/{asset_id}/risk-rules/draft`:编辑未上线风险规则草稿。
|
||||
- `POST /agent-assets/{asset_id}/risk-rules/revisions`:为已上线规则创建修订草稿。
|
||||
- `POST /agent-assets/{asset_id}/risk-rules/regenerate`:重新生成 DSL、流程图、风险评分和业务说明。
|
||||
- `POST /agent-assets/{asset_id}/risk-rules/feedback`、`GET /agent-assets/{asset_id}/risk-rules/feedback`:记录和查看误判/漏判反馈。
|
||||
|
||||
关键文件:
|
||||
|
||||
- 后端模板库与契约:`risk_rule_template_catalog.py`、`agent_asset.py`、`agent_asset_risk_rules.py`。
|
||||
- 后端生成、修订、发布、反馈、仿真:`risk_rule_generation*`、`agent_asset_risk_rule_revision.py`、`agent_asset_risk_rule_regeneration.py`、`agent_asset_risk_rule_publish.py`、`agent_asset_risk_rule_feedback.py`、`agent_asset_risk_rule_simulation.py`。
|
||||
- 前端新建、详情、测试:`AuditRuleDialogs.vue`、`AuditJsonRiskRuleDetail.vue`、`RiskRuleFlowDiagram.vue`、`RiskRuleTestDialog.vue`、`auditViewDetailTopBar.js`、`useAuditRiskRuleActions.js`、`useAuditAssetData.js`。
|
||||
- 测试:`test_risk_rule_template_catalog.py`、`test_risk_rule_feedback.py`、`test_risk_rule_revision_endpoints.py`、`test_risk_rule_explainability.py`、`risk-rule-detail-experience.test.mjs`。
|
||||
|
||||
已执行验证命令:
|
||||
|
||||
```bash
|
||||
docker exec x-financial-main bash -lc "cd /app/server && timeout 60 /tmp/x-financial-server-venv/bin/python -m pytest tests/test_risk_rule_template_catalog.py tests/test_openapi_schema.py -q"
|
||||
docker exec x-financial-main bash -lc "cd /app/server && timeout 60 /tmp/x-financial-server-venv/bin/python -m pytest tests/test_risk_rule_feedback.py tests/test_risk_rule_revision_endpoints.py tests/test_openapi_schema.py -q"
|
||||
docker exec x-financial-main bash -lc "cd /app/web && timeout 60 node --test tests/risk-rule-detail-experience.test.mjs"
|
||||
docker exec x-financial-main bash -lc "cd /app/web && timeout 60 npm run build"
|
||||
```
|
||||
|
||||
## 指标与验收
|
||||
|
||||
- [A1] 新建复杂差旅规则后,详情页流程解释不能出现“检查是否包含风险关键词”这类错误表达。
|
||||
- [A2] 详情页流程图与 JSON DSL 条件数量、分支方向、命中动作一致。
|
||||
- [A3] 仿真测试能显示票据识别字段,并说明为什么命中或未命中。
|
||||
- [A4] 同一条测试样例的执行 trace 可以高亮流程图路径。
|
||||
- [A5] 已上线规则修改时不会改变当前线上执行结果,只有发布修订版本后才替换。
|
||||
- [A6] 低、中、高、极高风险都能由评分模型产出,不应默认集中在中高风险。
|
||||
- [A7] 前端构建通过,后端定向测试 60s 内完成。
|
||||
|
||||
## 风险与开放问题
|
||||
|
||||
- LLM 语义理解仍可能出错,因此必须有 schema 校验、执行器 dry-run、详情解释和仿真测试兜底。
|
||||
- 字段本体不完整会限制规则表达,需要持续扩展费用、票据、预算、采购/AP 字段。
|
||||
- 复杂规则可能产生过大的流程图,需要主流程和完整 trace 分层展示。
|
||||
- 老规则没有 `semantic_plan` 或 `flow_model`,需要兼容展示并允许重新生成。
|
||||
- 常见规则模板要避免写成定制逻辑。模板只能提供默认文本、字段和 DSL 样例,最终仍走通用生成链路。
|
||||
|
||||
当前仍需持续演进的点:
|
||||
|
||||
- 企业卡、采购/AP、预算场景的字段本体还偏少,后续应补充企业卡交易流水、供应商、采购订单、合同、预算期间等字段。
|
||||
- 复杂规则的准确性仍依赖 Hermes 语义计划质量,执行前必须继续保留 DSL validator、执行器 dry-run 和仿真测试。
|
||||
- 模板库只作为规则编写入口的业务参考,不作为规则执行捷径;新增模板时必须同时提供 DSL 样例和 validator 测试。
|
||||
|
||||
## 实现确认
|
||||
|
||||
当前实现仍围绕“解释图和执行逻辑一致”推进:自然语言先经字段本体和语义计划形成受控 JSON DSL,详情页流程图、文字流程解释、测试 trace、上线版本均围绕同一份 DSL 展示和执行,没有新增流程图编辑器或绕过规则执行器的判断链路。
|
||||
@@ -1,96 +0,0 @@
|
||||
# 风险规则可解释流程判断改造 TODO
|
||||
|
||||
## 使用规则
|
||||
|
||||
- 每个 TODO 对应 `CONCEPT.md` 的目标、能力或验收点。
|
||||
- 只有真实完成并通过相应验证后,才能把 `[ ]` 改成 `[x]`。
|
||||
- 如果实现中发现需求变化,先更新 `CONCEPT.md`,再调整本 TODO。
|
||||
- 后端和构建验证默认在 Docker 容器 `x-financial-main` 的 `/app` 下执行。
|
||||
|
||||
## 1. 调研与边界
|
||||
|
||||
- [x] [CONCEPT: 背景与问题] 梳理当前风险规则生成链路,记录 `risk_rule_generation.py` 到 `risk_rule_template_executor.py` 的真实调用关系。证据:`CONCEPT.md` 后端设计与本轮落地结果记录生成、DSL validator、执行器、流程图、仿真测试链路。
|
||||
- [x] [CONCEPT: 前端设计] 梳理详情页、新建弹窗、测试弹窗当前字段来源,记录 `AuditRuleDialogs.vue`、`AuditJsonRiskRuleDetail.vue`、`RiskRuleTestDialog.vue` 的改造点。证据:`CONCEPT.md` 本轮落地结果列出三个组件及对应职责,`risk-rule-detail-experience.test.mjs` 覆盖关键接线。
|
||||
- [x] [CONCEPT: 数据设计] 确认 `AgentAssetRead`、版本内容、`config_json` 中已有字段,确定 `semantic_plan`、`flow_model`、`flow_diagram_svg` 的落点。证据:`AgentAssetRead` 返回 `latest_test_summary`,版本 JSON metadata 保存 `semantic_plan`/`flow_model`/`flow_diagram_svg`,生成测试覆盖。
|
||||
- [x] [CONCEPT: 非目标] 明确本期不做流程图编辑器,不增加拖拽、缩放、节点编辑能力。证据:`RiskRuleFlowDiagram.vue` 只渲染静态 SVG/文字说明,无编辑、拖拽、缩放入口;前端回归测试断言不存在 zoom 按钮。
|
||||
|
||||
## 2. 语义计划与 DSL 契约
|
||||
|
||||
- [x] [CONCEPT: C2] 定义 `semantic_plan` schema,包含规则意图、适用范围、字段本体映射、判断步骤、例外条件和风险动作。证据:`risk_rule_explainability.py` 产出 `semantic_plan`,`test_risk_rule_explainability.py` 已验证。
|
||||
- [x] [CONCEPT: C3] 定义 JSON DSL schema,补齐城市、日期、金额、附件、语义说明等通用算子。证据:`risk_rule_dsl_validator.py` 定义受控 DSL 校验,`risk_rule_generation_interpreter.py` 补充 `numeric_compare`,`risk_rule_template_executor.py` 支持日期、字段集合、附件存在性、文本例外和数值比较算子。
|
||||
- [x] [CONCEPT: C3] 增加 DSL validator,禁止复杂字段判断退化为“风险关键词匹配”。证据:`validate_risk_rule_draft` 会将城市一致性关键词规则改写为结构化比较,将预算金额关键词规则改写为 `composite_rule_v1`,`test_risk_rule_dsl_validator.py` 覆盖。
|
||||
- [x] [CONCEPT: C3] 为差旅城市不一致、住宿日期越界、预算阈值、重复发票各准备一条 DSL 样例。证据:新增 `risk_rule_dsl_examples.py`,并通过 `test_risk_rule_dsl_examples.py` 覆盖四类样例的 validator 与执行器命中/未命中回归。
|
||||
- [x] [CONCEPT: C2] 字段展示统一为 `中文[英文]` 格式,并复用字段本体解释。证据:`risk_rule_explainability.py` 的 `semantic_plan.required_fields.display` 使用字段本体生成 `label[key]`。
|
||||
|
||||
## 3. Hermes 生成链路
|
||||
|
||||
- [x] [CONCEPT: 总体链路] 调整 `risk_rule_generation_prompt.py`,要求 Hermes 先输出 `semantic_plan`,再输出 DSL。证据:提示词 `required_json_shape` 改为 `{ semantic_plan, dsl }`,`test_prompt_requires_semantic_plan_then_dsl` 验证。
|
||||
- [x] [CONCEPT: C2] 在提示词中明确:城市、日期、金额、票据关系必须用结构化比较,不允许用关键词替代。证据:`risk_rule_generation_prompt.py` 补充 `numeric_compare` 和预算金额不得关键词匹配的 guardrail。
|
||||
- [x] [CONCEPT: 后端设计] 在 `risk_rule_generation_semantics.py` 或解释层补齐语义计划解析与错误返回。证据:`risk_rule_generation_semantic_plan.py` 解析 `{ semantic_plan, dsl }` 包装并保留 `metadata.model_semantic_plan`;后台生成失败写入 `generation_error` 和 `last_operation=generation_failed`,容器内 `test_risk_rule_generation_failure.py` 与语义计划测试通过。
|
||||
- [x] [CONCEPT: 后端设计] 在 `risk_rule_generation_interpreter.py` 中从 `semantic_plan` 生成标准 DSL。证据:新增 `build_dsl_from_semantic_plan`,当 Hermes 仅返回 `semantic_plan` 时生成 `composite_rule_v1` 草稿,再由 DSL validator 基于字段本体规范成受控条件;`test_semantic_plan_only_response_can_generate_standard_dsl` 通过。
|
||||
- [x] [CONCEPT: 指标与验收] 增加复杂差旅规则生成测试,确认判断依据不是关键词匹配。证据:`test_generate_complex_travel_route_rule_uses_formula_not_keyword_match` 验证复杂差旅规则生成后为结构化城市一致性规则,且 `condition_summary` 不含“风险关键词”;容器内 `test_risk_rule_generation.py` 通过。
|
||||
|
||||
## 4. 流程模型与 SVG
|
||||
|
||||
- [x] [CONCEPT: C4] 定义 `flow_model` schema,包含 nodes、edges、字段引用、分支标签和风险节点。证据:`risk_rule_explainability.py` 产出 `flow_model`,生成测试验证 nodes/source/metadata 同步。
|
||||
- [x] [CONCEPT: C4] 修改 `risk_rule_flow_diagram.py`,改为从 DSL 或 `flow_model` 生成 SVG。证据:新增 `build_risk_rule_flow_diagram_spec`,优先从 `flow_model.nodes` 生成图形 spec,缺失时回退 `params.conditions`;`test_flow_diagram_spec_prefers_flow_model_nodes` 通过。
|
||||
- [x] [CONCEPT: C4] 保持 Style 7 / OpenAI Official 风格:白底、细边框、低饱和、风险节点单点强调。证据:`RiskRuleFlowDiagramRenderer` 输出白底、细边框、低饱和风险色,既有 `test_risk_rule_generation.py` 校验高风险红色、无旧绿色和无阴影滤镜。
|
||||
- [x] [CONCEPT: 算法与公式] 实现流程复杂度控制,节点过多时压缩主流程。证据:`_condition_lines_from_flow_nodes` 将超过 4 个判断节点压缩为摘要,`test_flow_diagram_spec_compresses_too_many_decision_nodes` 覆盖。
|
||||
- [x] [CONCEPT: C4] 为老规则缺少 `flow_model` 的情况保留默认静态图兜底。证据:`build_risk_rule_flow_diagram_spec` 在 `flow_model` 缺失时使用 DSL/metadata 生成 spec,`test_flow_diagram_spec_falls_back_to_dsl_when_flow_model_missing` 通过。
|
||||
|
||||
## 5. 执行器 trace 与仿真测试
|
||||
|
||||
- [x] [CONCEPT: C5] 修改 `RiskRuleTemplateExecutor`,输出每个判断节点的 trace。证据:新增 `evaluate_with_trace`,仿真测试返回 `trace.steps` 和 `path_node_ids`。
|
||||
- [x] [CONCEPT: C5] 仿真测试统一在“用户点击运行”后处理附件和文本,不允许上传后立即判断。证据:`RiskRuleTestDialog.vue` 的 `handleFileChange` 只把附件加入待发送列表,`sendMessage` 才调用 `recognizeTemporaryFiles` 与 `simulateRiskRuleTest`;容器内 `npm run build` 通过。
|
||||
- [x] [CONCEPT: C5] 测试结果中展示 OCR 原始字段、Hermes 规范化字段、执行器实际输入字段。证据:`AgentAssetRiskRuleSimulationRead` 新增 `ocr_raw_fields`、`hermes_normalized_fields`、`executor_input_fields`,`RiskRuleTestDialog.vue` 展示字段流水线;容器内 `test_risk_rule_explainability.py` 与 `test_risk_rule_generation.py` 通过。
|
||||
- [x] [CONCEPT: C5] 测试弹窗展示命中路径、未命中原因和最终风险动作。证据:`RiskRuleTestDialog.vue` 展示“执行路径”,`riskRuleTestDialogDisplay.js` 格式化 trace。
|
||||
- [x] [CONCEPT: C5] trace 中的 `node_id` 必须能映射到流程图节点。证据:`flow_model` 使用条件 id 作为节点 id,`risk_rule_execution_trace.py` 输出同名 `node_id`。
|
||||
|
||||
## 6. 规则修改与版本化
|
||||
|
||||
- [x] [CONCEPT: C6] 未上线规则支持编辑标题、费用领域、附件要求和自然语言描述。证据:新增 `AgentAssetRiskRuleRevisionService.update_unpublished_draft` 与 `PATCH /agent-assets/{asset_id}/risk-rules/draft`,容器内 `test_risk_rule_revision_endpoints.py` 覆盖返回字段。
|
||||
- [x] [CONCEPT: C6] 已上线规则新增“创建修订版本”,不直接覆盖 active 版本。证据:新增 `AgentAssetRiskRuleRevisionService.create_revision_draft` 与 `POST /agent-assets/{asset_id}/risk-rules/revisions`,测试验证 `published_version` 保持不变且 `working_version` 进入修订版本。
|
||||
- [x] [CONCEPT: C6] 修订版本保存后重新生成 DSL、流程图、风险评分和业务说明。证据:新增 `AgentAssetRiskRuleRegenerationService` 与 `POST /agent-assets/{asset_id}/risk-rules/regenerate`,草稿/修订草稿都会重新生成 JSON DSL、`flow_diagram_svg`、风险评分和版本 Markdown;容器内 `test_regenerate_revision_draft_keeps_active_document_unchanged` 通过。
|
||||
- [x] [CONCEPT: C6] 发布修订版本时归档旧版本,并记录修改人、修改原因和测试报告。证据:新增 `AgentAssetRiskRulePublishMixin`,发布修订时将旧 `rule_document` 写入 `revision_history.previous_rule_document`,切换新 JSON 文件并写入 `last_operation=publish_revision`;容器内 `test_publish_regenerated_revision_replaces_online_document` 通过。
|
||||
- [x] [CONCEPT: C6] 普通用户误判/漏判反馈进入规则反馈记录,不直接修改规则。证据:新增 `agent_asset_rule_feedback` 模型、`POST/GET /agent-assets/{asset_id}/risk-rules/feedback`、前端服务方法;容器内 `test_risk_rule_feedback.py`、规则回归和 `npm run build` 通过。
|
||||
|
||||
## 7. 常见费控规则模板库
|
||||
|
||||
- [x] [CONCEPT: C1] 增加“从常见规则模板创建”入口。证据:`AuditRuleDialogs.vue` 新建风险规则弹窗新增常见规则模板选择,选择后预填标题、附件要求、业务环节、费用领域和自然语言。
|
||||
- [x] [CONCEPT: C1] 模板按预算、票据、差旅、招待、采购/AP、企业卡、通用分组。证据:新增 `risk_rule_template_catalog.py`,`GET /agent-assets/risk-rules/templates` 返回 7 个分组;容器内 `test_risk_rule_template_catalog.py` 通过。
|
||||
- [x] [CONCEPT: C3] 每个模板提供默认自然语言、字段清单、附件要求和 DSL 样例。证据:模板接口返回 `natural_language`、`fields`、`requires_attachment`、`dsl_example`;容器内测试逐个调用 DSL validator 验证通过。
|
||||
- [x] [CONCEPT: 非目标] 模板不得绕过通用生成链路,不写定制校准器。证据:前端模板只预填 `riskRuleCreateForm`,提交仍走 `generateRiskRuleAsset`;无新增定制校准器,容器内 `npm run build` 通过。
|
||||
|
||||
## 8. 前端详情与交互
|
||||
|
||||
- [x] [CONCEPT: 前端设计] 详情页 topbar 展示规则标题、状态、风险分数、风险等级、上线/启用状态。证据:`auditViewDetailTopBar.js` 为风险规则详情输出风险分、风险等级、规则状态、上线状态、启用状态 KPI;容器内 `npm run build` 通过。
|
||||
- [x] [CONCEPT: C4] 判断流程区域改成左侧文字流程解释、右侧流程图。证据:`RiskRuleFlowDiagram.vue` 使用左侧 `risk-rule-flow-explainer` 和右侧 `risk-rule-flow-visual` 的两栏布局;容器内 `npm run build` 通过。
|
||||
- [x] [CONCEPT: C4] 流程图标题固定为“流程图”,高度与“流程解释”标题对齐。证据:`RiskRuleFlowDiagram.vue` 使用统一 `risk-rule-section-title`,右侧标题固定为“流程图”;容器内 `npm run build` 通过。
|
||||
- [x] [CONCEPT: C5] 测试弹窗展示字段识别结果、规范化字段、判断路径和测试报告。证据:`RiskRuleTestDialog.vue` 展示字段流水线、执行路径和右侧测试报告摘要;容器内 `cd /app/web && npm run build` 通过。
|
||||
- [x] [CONCEPT: C6] 已上线规则详情展示“创建修订版本”,草稿规则展示“编辑规则”。证据:`AuditView.vue` 底部动作区按规则状态展示按钮,`AuditRuleDialogs.vue` 提供编辑/修订弹窗,`useAuditRiskRuleActions.js` 调用草稿编辑与修订接口;容器内 `cd /app/web && npm run build` 通过。
|
||||
- [x] [CONCEPT: 指标与验收] 列表和详情状态刷新不能造成页面闪烁。证据:`useAuditAssetData.loadSelectedAssetDetail` 增加 `{ silent: true }` 静默刷新,规则保存、送审、审核、上线、回退和版本操作均改为静默刷新;容器内 `npm run build` 通过。
|
||||
|
||||
## 9. 后端接口与权限
|
||||
|
||||
- [x] [CONCEPT: 接口设计] 实现或调整 `POST /agent-assets/{asset_id}/risk-rules/revisions`。证据:新增独立路由 `agent_asset_risk_rules.py`,容器内 `test_create_risk_rule_revision_endpoint_keeps_active_version` 通过。
|
||||
- [x] [CONCEPT: 接口设计] 实现或调整 `PATCH /agent-assets/{asset_id}/risk-rules/draft`。证据:新增独立路由 `agent_asset_risk_rules.py`,容器内 `test_update_risk_rule_draft_endpoint_updates_unpublished_rule` 与已上线阻断用例通过。
|
||||
- [x] [CONCEPT: 接口设计] `POST /agent-assets/{asset_id}/risk-rules/regenerate` 返回生成状态和错误详情。证据:独立路由 `agent_asset_risk_rules.py` 已接入重生成接口,成功返回 `AgentAssetRead.config_json.generation_status`/`revision_draft.generation_status`,接口用例 `test_regenerate_risk_rule_endpoint_returns_updated_detail` 通过。
|
||||
- [x] [CONCEPT: 接口设计] 仿真测试接口返回 `recognized_fields`、`normalized_fields`、`execution_result`、`trace`。证据:`AgentAssetRiskRuleSimulationRead` 新增 `normalized_fields` 和 `trace`,仿真测试覆盖返回值。
|
||||
- [x] [CONCEPT: 用户与场景] 普通财务人员只能编辑未上线/修订草稿,admin 才能删除和测试,管理员按现有权限上线/下线。证据:路由依赖使用 `RuleEditorUser`、`RuleReviewerUser`、`PlatformAdminUser` 分层,`test_risk_rule_revision_endpoints.py` 覆盖 finance 新建/测试阻断、manager 删除阻断和 manager 启停入口。
|
||||
- [x] [CONCEPT: 数据设计] 所有操作写入 `last_operation`,用于详情页“最后操作”展示。证据:生成、后台生成、草稿编辑、创建修订、重新生成、发布/下线、测试确认等风险规则服务均写入 `config_json.last_operation`,前端 `AuditJsonRiskRuleDetail.vue` 展示 `lastOperationLabel`。
|
||||
|
||||
## 10. 测试与验证
|
||||
|
||||
- [x] [CONCEPT: 测试方案] 后端补充语义计划、DSL validator、执行器 trace、流程图转换单元测试。证据:`test_risk_rule_explainability.py` 覆盖语义计划、flow_model、trace;`test_risk_rule_dsl_validator.py` 覆盖 DSL validator 与 `numeric_compare` 执行;容器内相关测试通过。
|
||||
- [x] [CONCEPT: 测试方案] 后端补充修订版本接口和发布替换接口测试。证据:`test_risk_rule_revision_service.py` 覆盖草稿编辑、创建修订、修订重生成和发布替换;`test_risk_rule_revision_endpoints.py` 覆盖草稿编辑、创建修订和重生成接口;容器内相关测试通过。
|
||||
- [x] [CONCEPT: 测试方案] 前端补充详情页流程展示、测试弹窗字段展示、修订版本按钮状态测试。证据:新增 `risk-rule-detail-experience.test.mjs` 覆盖 topbar KPI、左文右图流程、字段流水线和修订按钮;容器内 `node --test tests/risk-rule-detail-experience.test.mjs` 通过。
|
||||
- [x] [CONCEPT: 容器验证] 在容器执行后端定向测试,单个命令设置 60s 超时。证据:`/tmp/x-financial-server-venv/bin/python -m pytest tests/test_risk_rule_explainability.py -q`、`test_risk_rule_composite_generation.py -q`、`test_risk_rule_generation.py -q` 均通过。
|
||||
- [x] [CONCEPT: 容器验证] 在容器执行 `cd /app/web && npm run build`。证据:容器 `/app/web` 构建通过。
|
||||
- [x] [CONCEPT: 指标与验收] 用“武汉到上海票据 + 北京出差 3 天”样例验证城市不一致规则必须命中或给出明确不命中原因。证据:`test_simulation_returns_execution_trace_for_ticket_city_mismatch` 验证命中并返回 trace。
|
||||
- [x] [CONCEPT: 指标与验收] 用预算阈值、重复发票、住宿日期越界、招待人均超标样例做回归。证据:`risk_rule_dsl_examples.py` 已包含预算阈值、重复发票、住宿日期越界、招待人均超标样例,`test_risk_rule_dsl_examples.py` 在容器内 7 passed。
|
||||
|
||||
## 11. 文档收尾
|
||||
|
||||
- [x] [CONCEPT: 指标与验收] 开发完成后补充实际接口、文件和测试命令结果。证据:`CONCEPT.md` 新增“本轮落地结果”,列出接口、关键文件和容器验证命令。
|
||||
- [x] [CONCEPT: 风险与开放问题] 记录暂未解决的字段本体缺口和复杂规则降级策略。证据:`CONCEPT.md` 风险与开放问题补充企业卡、采购/AP、预算字段本体缺口和 DSL validator/dry-run/仿真兜底策略。
|
||||
- [x] [CONCEPT: 功能一句话] 确认最终实现没有偏离“解释图和执行逻辑一致”的核心目标。证据:`CONCEPT.md` 新增“实现确认”,明确自然语言、字段本体、JSON DSL、流程图、测试 trace 和上线版本围绕同一 DSL。
|
||||
@@ -1,27 +0,0 @@
|
||||
# 差旅申请后行程规划推荐
|
||||
|
||||
## 背景
|
||||
|
||||
用户完成差旅申请后,当前流程直接结束,交互偏机械。差旅申请本身已经包含地点、行程时间、出行方式、天数等信息,系统可以在申请提交成功后继续以对话形式询问是否需要行程规划。
|
||||
|
||||
## 目标
|
||||
|
||||
- 仅在差旅费用申请提交成功后追加一条对话式推荐。
|
||||
- 推荐内容应基于本次申请的已知字段,不要求用户重新输入地点和时间。
|
||||
- 用户同意后,在当前申请助手对话中生成规划建议。
|
||||
- 规划建议只提供交通时间窗口、酒店区域、待确认事项,不创建订单、不保存草稿、不调用真实订票接口。
|
||||
|
||||
## 非目标
|
||||
|
||||
- 不接入真实火车、机票、酒店预订。
|
||||
- 不改变申请单提交和审批状态。
|
||||
- 不强制用户继续规划。
|
||||
|
||||
## 交互
|
||||
|
||||
1. 用户确认提交差旅申请。
|
||||
2. 系统返回申请提交成功结果。
|
||||
3. 系统追加一条轻量对话:询问是否需要行程规划。
|
||||
4. 用户点击“生成行程规划”后,系统在对话中给出推荐。
|
||||
5. 用户点击“暂不需要”后,系统简短确认,不再继续追问。
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
# 差旅申请后行程规划推荐 TODO
|
||||
|
||||
- [x] 新增差旅规划推荐工具,按申请预览字段生成提示、动作和规划正文。
|
||||
- [x] 申请提交成功后追加规划推荐对话。
|
||||
- [x] 支持“生成行程规划”和“暂不需要”两个对话动作。
|
||||
- [x] 增加前端静态测试覆盖,防止回退成死板结束流程。
|
||||
- [x] 运行定向测试和前端构建验证。
|
||||
|
||||
@@ -1,154 +0,0 @@
|
||||
# 半年报销模拟数据概念文档
|
||||
|
||||
## 功能一句话
|
||||
|
||||
为本地演示环境生成 2026 年上半年公司报销、预算和员工组织样本,让财务看板与预算中心能直接呈现半年经营分析效果。
|
||||
|
||||
## 背景与问题
|
||||
|
||||
当前容器数据库已有员工与预算基础表,但报销样本很少,无法观察半年维度的费用趋势、部门支出结构、预算使用率和风险预警效果。用户希望把公司人数扩充到 100 人,并模拟半年报销数据,用于查看整体分析和预算管控效果。
|
||||
|
||||
现状只读检查结果:
|
||||
|
||||
- `employees=82`
|
||||
- `expense_claims=3`
|
||||
- `budget_allocations=240`
|
||||
- `budget_transactions=241`
|
||||
- `risk_observations=0`
|
||||
- 尚无 `SIM2026` 员工、`SIM-EXP-2026` 报销单和 `SIM-BUD-2026` 预算数据。
|
||||
|
||||
## 目标与非目标
|
||||
|
||||
目标:
|
||||
|
||||
- 把本地演示公司员工补齐到 100 人,不删除已有员工。
|
||||
- 生成 2026 年 1 月到 6 月的报销单、报销明细和风险观察样本。
|
||||
- 生成或复用预算额度,并写入预算核销台账,让预算中心能看到真实使用率、预警和超支。
|
||||
- 保证脚本默认 dry-run,只有显式 `--apply` 才写数据库。
|
||||
- 生成完成后能用容器内 DB 统计和真实 API 返回值验证。
|
||||
|
||||
非目标:
|
||||
|
||||
- 不接入真实生产 API,不导入真实个人敏感数据。
|
||||
- 不删除或重置用户已有数据;如未来需要清理模拟数据,应另走显式确认。
|
||||
- 不改造预算中心、财务看板和报销审批页面结构。
|
||||
- 不把模拟数据写入启动流程,避免每次启动自动膨胀数据。
|
||||
|
||||
## 用户与场景
|
||||
|
||||
- 财务负责人:查看半年费用趋势、待审批金额、风险数量和 SLA。
|
||||
- 预算管理者:查看部门和费用科目的预算使用率、预警线和剩余额度。
|
||||
- 产品演示者:用 100 人组织规模演示智能费控、预算中心和分析看板的联动。
|
||||
|
||||
## 功能能力
|
||||
|
||||
### 输入
|
||||
|
||||
- 目标员工数:默认 100。
|
||||
- 模拟窗口:默认 `2026-01-01` 到 `2026-06-30`。
|
||||
- 随机种子:固定值,确保样本可复现。
|
||||
- 执行模式:默认 dry-run,`--apply` 写入数据库。
|
||||
|
||||
### 输出
|
||||
|
||||
- 新增员工:只补齐缺口,员工编号前缀 `SIM2026`。
|
||||
- 新增报销单:编号前缀 `SIM-EXP-2026`。
|
||||
- 新增明细:按报销单生成 1 到 3 条费用明细。
|
||||
- 新增预算额度:编号前缀 `SIM-BUD-2026`,按部门、季度、费用科目覆盖差旅、招待、办公和通信。
|
||||
- 新增预算交易:编号前缀 `SIM-BTX-2026`,对已通过、待付款、已付款和完成状态写入 `consume` 台账,对待审批状态写入 `reserve` 台账。
|
||||
- 新增风险观察:编号前缀 `SIM-RISK-2026`,用于财务看板风险混合和异常数统计。
|
||||
|
||||
### 边界
|
||||
|
||||
- 如果员工数已经大于等于 100,只新增 0 人,不删除已有员工。
|
||||
- 如果同编号模拟数据已存在,脚本跳过,保证重复执行不重复膨胀。
|
||||
- 预算使用率通过交易台账计算,不直接改写预算余额字段。
|
||||
- 预算超支样本允许存在,用于展示预算效果和预警,但需要控制比例,避免所有部门都显示异常。
|
||||
|
||||
## 方案设计
|
||||
|
||||
### 后端脚本
|
||||
|
||||
新增独立服务模块:
|
||||
|
||||
- `demo_company_simulation_seed.py`:封装模拟数据规划、dry-run 统计和 apply 写入。
|
||||
|
||||
新增命令脚本:
|
||||
|
||||
- `seed_half_year_expense_demo.py`:解析参数并调用服务模块。
|
||||
|
||||
### 数据策略
|
||||
|
||||
- 组织:复用现有 `OrganizationUnit`,优先使用部门节点和成本中心。
|
||||
- 员工:补齐到 100 人,按部门规模权重分配,职级覆盖 P3-P8。
|
||||
- 报销单:按员工、月份、费用类型生成,低频员工 1-2 单,高频角色 4-8 单。
|
||||
- 风险:约 12%-18% 的报销单带风险标记和 `RiskObservation`。
|
||||
- 预算:按部门、季度、科目创建模拟预算额度,Q2 相比 Q1 有 8%-18% 增长,部分市场、技术部门科目接近 80% 预警线。
|
||||
|
||||
### 运行命令
|
||||
|
||||
```bash
|
||||
docker exec -w /app -e SERVER_VENV_DIR=/tmp/x-financial-server-venv x-financial-main \
|
||||
/tmp/x-financial-server-venv/bin/python server/scripts/seed_half_year_expense_demo.py
|
||||
```
|
||||
|
||||
写入时使用:
|
||||
|
||||
```bash
|
||||
docker exec -w /app -e SERVER_VENV_DIR=/tmp/x-financial-server-venv x-financial-main \
|
||||
/tmp/x-financial-server-venv/bin/python server/scripts/seed_half_year_expense_demo.py --apply
|
||||
```
|
||||
|
||||
## 算法与公式
|
||||
|
||||
### 员工缺口
|
||||
|
||||
$$
|
||||
new\_employees = \max(target\_employees - current\_employees,\ 0)
|
||||
$$
|
||||
|
||||
### 报销金额
|
||||
|
||||
每类费用按基础金额、部门系数、职级系数和月度季节系数生成:
|
||||
|
||||
$$
|
||||
claim\_amount = base\_amount(type) \times dept\_factor \times grade\_factor \times month\_factor \times noise
|
||||
$$
|
||||
|
||||
### 预算使用率
|
||||
|
||||
预算中心沿用现有计算口径:
|
||||
|
||||
$$
|
||||
usage\_rate = \frac{reserved\_amount + consumed\_amount}{original\_amount + adjusted\_amount} \times 100
|
||||
$$
|
||||
|
||||
### 风险样本概率
|
||||
|
||||
风险概率按金额分位和预算压力提升:
|
||||
|
||||
$$
|
||||
risk\_probability = base\_risk + amount\_boost + budget\_pressure\_boost
|
||||
$$
|
||||
|
||||
## 测试方案
|
||||
|
||||
- 单元测试:在 SQLite 内存库里验证 dry-run、员工补齐、幂等写入和预算交易统计。
|
||||
- 容器验证:在 `x-financial-main` 内运行定向测试,单次不超过 60s。
|
||||
- 运行时验证:执行 dry-run 后检查计划数量;执行 apply 前必须人工确认。
|
||||
- API 验证:写入后请求财务看板和预算汇总接口,确认 JSON 中员工、报销、预算使用率和风险指标有数据。
|
||||
|
||||
## 指标与验收
|
||||
|
||||
- 员工总数达到 100。
|
||||
- `SIM-EXP-2026` 半年报销单不少于 300 单。
|
||||
- 预算汇总接口返回 Q1、Q2 趋势,且至少有 1 条预算预警。
|
||||
- 财务看板 `has_real_data=true`,风险数、费用分类、部门排行和预算摘要均非空。
|
||||
- 重复执行脚本不会新增重复模拟数据。
|
||||
|
||||
## 风险与开放问题
|
||||
|
||||
- 批量写入数据库属于高风险操作,执行 `--apply` 前必须获得用户明确确认。
|
||||
- 如果当前数据库已有大量非模拟员工,脚本不会删除员工来凑精确 100 人,只保证不少于目标数。
|
||||
- 财务看板趋势接口当前最多按 90 天标签解析;半年分析主要依赖预算中心 Q1/Q2 趋势和自定义日期范围。
|
||||
- 如果后续要支持页面一键生成,需要另行设计权限、审计和清理机制。
|
||||
@@ -1,23 +0,0 @@
|
||||
# 半年报销模拟数据 TODO
|
||||
|
||||
## 调研与契约
|
||||
|
||||
- [x] [CONCEPT: 背景与问题] 读取员工、报销、预算和财务看板现有模型,确认模拟数据要写入 `employees`、`expense_claims`、`expense_claim_items`、`budget_allocations`、`budget_transactions`、`budget_reservations` 和 `risk_observations`。
|
||||
- [x] [CONCEPT: 背景与问题] 在 `x-financial-main` 容器内完成只读规模检查,当前员工 82 人、报销单 3 单、模拟前缀数据为 0。
|
||||
- [x] [CONCEPT: 方案设计] 明确脚本默认 dry-run,批量写入必须使用 `--apply` 并先得到用户确认。
|
||||
|
||||
## 数据生成
|
||||
|
||||
- [x] [CONCEPT: 数据策略] 新增模拟数据服务模块,封装员工、预算、报销、明细、风险观察的生成逻辑。证据:`demo_company_simulation_seed.py` 与 `demo_company_simulation_catalog.py`。
|
||||
- [x] [CONCEPT: 输入] 新增命令脚本,支持 `--target-employees`、`--start-date`、`--months`、`--seed`、`--apply`。证据:`seed_half_year_expense_demo.py`。
|
||||
- [x] [CONCEPT: 边界] 实现幂等逻辑:已存在的 `SIM2026`、`SIM-EXP-2026` 和 `SIM-BUD-2026` 数据不重复创建。证据:`test_half_year_simulation_preview_and_apply_are_idempotent`。
|
||||
- [x] [CONCEPT: 预算使用率] 通过 `BudgetTransaction` 和 `BudgetReservation` 形成预算使用效果,不直接改余额。证据:`test_half_year_simulation_feeds_budget_summary`。
|
||||
|
||||
## 验证
|
||||
|
||||
- [x] [CONCEPT: 测试方案] 新增定向单元测试,覆盖 dry-run、apply、员工补齐和幂等性。证据:`server/tests/test_demo_company_simulation_seed.py`。
|
||||
- [x] [CONCEPT: 测试方案] 在容器中以 60s 超时运行定向测试。证据:`pytest -q server/tests/test_demo_company_simulation_seed.py` 通过,2 passed。
|
||||
- [x] [CONCEPT: 运行命令] 执行 dry-run,输出计划写入规模。证据:dry-run 计划新增 18 名员工、495 张报销单、855 条明细、34 个预算池、459 条预算交易、83 条预占、55 条风险观察。
|
||||
- [x] [CONCEPT: 风险与开放问题] 获得用户确认后执行 `--apply` 写入本地数据库。证据:`seed_half_year_expense_demo.py --apply` 成功写入。
|
||||
- [x] [CONCEPT: 指标与验收] 用容器内 DB 统计确认员工数、模拟报销单、预算交易和风险观察。证据:员工 100 人,模拟报销 495 单、预算交易 459 条、风险观察 55 条。
|
||||
- [x] [CONCEPT: 指标与验收] 用真实 API 验证财务看板与预算汇总 JSON 已出现半年模拟数据效果。证据:预算汇总 API 返回 `warning_count=10`、`over_budget_count=3`;财务看板 API 返回 `has_real_data=true`、`riskCount=57`。
|
||||
@@ -1,424 +0,0 @@
|
||||
# 小财管家
|
||||
|
||||
## 功能一句话
|
||||
|
||||
小财管家是首页统一财务任务入口,负责把用户的自然语言和附件拆解为多个可确认、可追踪、可分派的申请与报销任务,再调用现有申请助手和报销助手完成执行闭环。
|
||||
|
||||
## 背景与问题
|
||||
|
||||
当前个人工作台已经提供首页输入框,并能通过本体解析把一句话路由到申请、报销、预算或知识等单一会话。这个能力适合单意图,但用户真实表达经常是多任务组合,例如同时包含出差申请、昨日交通费报销、历史出差费用报销以及多张附件。
|
||||
|
||||
现有问题:
|
||||
|
||||
- 首页输入框当前会收敛为一个 `sessionType`,无法保留多任务计划。
|
||||
- 申请助手和报销助手已经具备单任务核对能力,但缺少上层任务拆解、归集和跨助手分派。
|
||||
- 附件上传后主要进入当前会话,缺少面向多任务的自动归集建议。
|
||||
- 财务动作需要确认后才能入库、绑定或提交,不能让大模型直接执行高风险动作。
|
||||
- 新增字段必须尊重本体字段,不能因为小财管家新增一套业务字段。
|
||||
|
||||
## 目标与非目标
|
||||
|
||||
### 目标
|
||||
|
||||
- 首页输入框定位为“小财管家”,作为用户默认财务任务入口。
|
||||
- 用户提交自然语言和附件后,先展示小财管家的任务识别与附件归集过程。
|
||||
- 支持把一句话拆成多个任务,第一版只验证费用申请和费用报销。
|
||||
- 支持多附件按费用场景、时间、地点、任务线索形成归集建议。
|
||||
- 遇到创建申请单、创建报销草稿、附件绑定、提交审批等动作时必须等待用户确认。
|
||||
- 保留现有申请助手和报销助手能力,小财管家只做上层编排和分派。
|
||||
- 外层意图识别必须优先使用大模型 function calling 输出结构化任务计划,规则逻辑只作为模型不可用或结构不合法时的兜底。
|
||||
- 前端展示“意图识别智能体”过程气泡,并用流式状态逐步呈现,不暴露模型内部推理链。
|
||||
- 所有业务字段先进入本体字段归一化,再进入下游助手、草稿、风险规则和持久化。
|
||||
|
||||
### 非目标
|
||||
|
||||
- 第一版不做万能智能体,不覆盖预算、审批、知识问答等全部场景。
|
||||
- 第一版不引入 LangChain 或 LangGraph,先复用项目内运行时模型配置和 OpenAI-compatible function calling 契约。
|
||||
- 第一版不自动提交审批,不绕过用户确认。
|
||||
- 第一版不新增业务语义字段;只新增任务编排态字段。
|
||||
- 第一版不重写申请助手、报销助手和现有 Orchestrator。
|
||||
|
||||
## 用户与场景
|
||||
|
||||
### 目标用户
|
||||
|
||||
- 普通员工:在首页一次性描述申请、报销和附件处理诉求。
|
||||
- 财务人员:查看任务拆解、附件归集和用户确认链路是否可追溯。
|
||||
- 审批/管理角色:后续可扩展为审批待办和预算提醒编排,但不进入第一版。
|
||||
|
||||
### 核心场景
|
||||
|
||||
用户在首页输入:
|
||||
|
||||
```text
|
||||
我想要申请7月2日去北京出差,辅助北京供电局的税务审核任务,并且我要报销昨天的交通费,还需要报销6月3日出差去上海的费用
|
||||
```
|
||||
|
||||
系统处理:
|
||||
|
||||
1. 小财管家识别到三条候选任务。
|
||||
2. 将“昨天”按客户端日期解析为明确日期,例如 2026-06-03。
|
||||
3. 将“7月2日去北京出差”归为费用申请任务。
|
||||
4. 将“昨天的交通费”和“6月3日去上海出差费用”归为费用报销任务。
|
||||
5. 如果用户同时上传附件,系统先识别附件场景,再建议归集到对应任务。
|
||||
6. 需要创建申请单或报销草稿时,向用户展示核对摘要和确认动作。
|
||||
|
||||
## 功能能力
|
||||
|
||||
### 1. 任务识别与拆分
|
||||
|
||||
任务识别主链路是“小财管家意图识别智能体”:
|
||||
|
||||
1. 后端读取系统设置中的主模型/备模型运行时配置。
|
||||
2. 将用户话术、客户端日期、附件元信息、上下文和 canonical ontology field 列表传入模型。
|
||||
3. 通过强制 function calling 调用 `submit_steward_intent_plan`。
|
||||
4. 模型只能返回结构化参数:`thinking_events`、`tasks`、`attachment_groups`。
|
||||
5. 服务端再次校验:任务类型只能是 `expense_application` / `reimbursement`,业务字段只能是 canonical ontology fields,附件名必须来自本次上传。
|
||||
6. 如果模型未配置、调用失败、未返回工具调用或结构不合法,才切换到规则兜底,并在过程摘要中标记兜底原因。
|
||||
|
||||
输入:
|
||||
|
||||
- 用户自然语言 `message`
|
||||
- 附件元信息 `attachments`
|
||||
- 当前用户、部门、角色、客户端时间
|
||||
- 已有会话上下文,可选
|
||||
|
||||
输出:
|
||||
|
||||
- `plan_id`:本次小财管家计划 ID
|
||||
- `tasks`:多个任务条目
|
||||
- `thinking_events`:面向用户展示的过程摘要
|
||||
- `confirmation_groups`:需要用户确认的动作集合
|
||||
- `attachment_groups`:附件归集建议
|
||||
|
||||
任务条目包含:
|
||||
|
||||
- `task_id`:编排态任务 ID
|
||||
- `task_type`:`expense_application` 或 `reimbursement`
|
||||
- `assigned_agent`:`application_assistant` 或 `reimbursement_assistant`
|
||||
- `title`:任务标题
|
||||
- `summary`:任务摘要
|
||||
- `status`:`planned`、`needs_confirmation`、`ready_to_delegate`、`delegated`、`completed`、`blocked`
|
||||
- `confidence`:识别置信度
|
||||
- `ontology_fields`:归一化后的本体字段
|
||||
- `missing_fields`:缺失字段
|
||||
- `confirmation_required`:是否需要确认后执行
|
||||
|
||||
### 2. 附件归集
|
||||
|
||||
附件归集基于以下信号:
|
||||
|
||||
- 附件类型:发票、火车票、机票、酒店票、付款截图、招待票据等。
|
||||
- 费用场景:差旅、交通、招待、住宿、其他。
|
||||
- 日期:票据日期是否匹配任务时间。
|
||||
- 地点:票据地点是否匹配任务地点。
|
||||
- 金额:是否能参与报销草稿。
|
||||
- 置信度:低置信度必须提示用户核对。
|
||||
|
||||
输出示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"group_id": "ag_travel_001",
|
||||
"target_task_id": "task_reim_002",
|
||||
"scene": "travel",
|
||||
"scene_label": "差旅费用",
|
||||
"attachment_names": ["上海高铁票.jpg", "上海酒店发票.pdf", "出租车票.png"],
|
||||
"excluded_attachment_names": ["客户招待发票.jpg"],
|
||||
"confidence": 0.86,
|
||||
"confirmation_required": true
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 用户确认
|
||||
|
||||
必须确认的动作:
|
||||
|
||||
- 创建费用申请单。
|
||||
- 创建报销草稿。
|
||||
- 将附件归集并绑定到某一任务。
|
||||
- 将附件关联到已有报销草稿。
|
||||
- 提交审批。
|
||||
- 修改已有草稿字段。
|
||||
|
||||
小财管家的确认动作采用“下一步优先”策略:
|
||||
|
||||
- 同一个计划里同时存在申请和报销时,前端只展示当前下一步主动作,不一次性摊开全部确认按钮。
|
||||
- 下一步优先级为:费用申请单创建 > 报销单填写 > 附件归集确认。
|
||||
- 小财管家先思考和分析,说明下一步将要做的行为;用户输入“确定”或点击确认后,才进入行动。
|
||||
- 行动完成后重新检查剩余任务队列,继续进入“思考 -> 分析 -> 等待确认 -> 行动”的循环。
|
||||
- 申请任务完成后,再把剩余报销任务作为后续任务引导到报销助手。
|
||||
- 附件归集不作为第一屏主动作抢占申请流程;当进入报销任务时,相关附件随报销上下文带入。
|
||||
|
||||
小财管家必须维护运行时任务上下文,而不是把每次用户输入都当成新的独立意图。上下文至少包含:
|
||||
|
||||
- 当前任务:正在处理的申请或报销任务。
|
||||
- 剩余任务:已拆解但尚未处理的任务队列。
|
||||
- 已完成任务:已经形成申请单、报销草稿、附件归集或提交动作的任务。
|
||||
- 等待动作:当前正在等待用户补字段、确认核对表、确认提交审批或继续下一项。
|
||||
- 最近结构化结果:当前申请核对表、报销核对结果、附件归集建议等。
|
||||
|
||||
用户输入“确认”“无误”“可以提交”等文本时,小财管家必须先匹配当前等待动作;如果当前等待的是申请单提交确认,就提交当前申请单;如果当前等待的是继续下一项,就进入剩余任务队列中的下一项;如果当前核对表仍有缺字段,则提示补字段。只有没有可匹配上下文时,才重新进入任务规划。
|
||||
|
||||
上述匹配不应主要依赖前端关键词规则。第一版应新增“小财管家运行时决策智能体”,由后端 function calling 接收 `runtime_state` 和用户当前输入,返回结构化 `next_action`:
|
||||
|
||||
- `submit_current_application`:确认当前申请核对表并提交至审批。
|
||||
- `continue_next_task`:当前任务已完成,继续剩余任务队列中的下一项。
|
||||
- `fill_current_slot`:用户补充了当前等待字段。
|
||||
- `ask_user`:当前信息不足,需要继续追问。
|
||||
- `plan_new_tasks`:当前没有可匹配上下文,重新进入任务规划。
|
||||
- `cancel_current_action` / `no_op`:取消或不执行当前动作。
|
||||
|
||||
前端只执行模型返回的结构化动作,并做安全校验:例如申请核对表必须 `readyToSubmit` 才能提交,已提交消息必须标记避免重复提交,缺字段时必须追问。本地规则只允许作为模型失败后的保守兜底,不作为主判断来源。
|
||||
|
||||
可以自动执行的动作:
|
||||
|
||||
- 任务拆解。
|
||||
- 本体字段归一化。
|
||||
- 附件分类。
|
||||
- 缺失字段检查。
|
||||
- 风险和规则预审。
|
||||
- 生成确认摘要。
|
||||
|
||||
### 4. 流式过程摘要
|
||||
|
||||
前端展示的是“意图识别智能体”的过程摘要,不是模型内部推理链;过程摘要必须独立于最终回复正文展示。过程摘要必须围绕业务理解展开,例如用户说了什么、被拆成哪些申请/报销任务、已识别哪些业务要素、还缺少哪些关键条件、为什么需要向用户追问。不能只展示“接收确认、协调能力、准备输出”等系统执行日志。
|
||||
|
||||
示例:
|
||||
|
||||
```text
|
||||
正在识别用户输入中的财务事项...
|
||||
已识别到 3 个候选任务。
|
||||
正在按时间、地点和费用场景核对附件...
|
||||
发现 3 张附件疑似属于差旅费用,1 张附件需要单独处理。
|
||||
等待你确认后,我再创建申请单或报销草稿。
|
||||
```
|
||||
|
||||
第一版通过 `POST /steward/plans/stream` 返回 `application/x-ndjson` 流式事件:
|
||||
|
||||
- `thinking`:逐条追加到系统回复气泡上方的独立“意图识别智能体”折叠气泡。
|
||||
- `plan`:返回完整任务计划后,再渲染最终正文、任务卡片、附件归集和确认动作。
|
||||
|
||||
流式接口必须在模型 function calling 完成前先返回首个 `thinking` 事件,告知用户“意图识别智能体接管”。后续模型返回后再追加结构化拆解、字段映射、附件归集等过程摘要。
|
||||
|
||||
前端收到 `thinking` 事件后,也必须以 typewriter 方式逐字展示过程摘要,不能把一条完整思考事件一次性塞进折叠气泡。多条 `thinking` 事件应排队顺序输出,上一条内容打完后再输出下一条。
|
||||
|
||||
前端流式超时必须区分“首包等待”和“流式空闲等待”:首包应快速返回,收到首包后不能再用固定总时长中断仍在思考的模型调用,只能在长时间没有任何新事件时判定空闲超时。
|
||||
|
||||
流式过程中正文区域不输出任务结论;计划完成后意图识别气泡默认折叠,正文只保留用户需要确认和执行的信息。
|
||||
|
||||
计划完成后的最终正文也必须流式输出。前端不能把完整正文一次性替换到消息气泡里,而应进入 `typing` 状态按字符逐步追加正文;正文输出完成后,再把状态改为“等待用户确认”并展示确认按钮。
|
||||
|
||||
用户确认当前步骤后,小财管家隐式委派给申请能力或报销能力时,也必须保持同一套流式体验:先在系统气泡上方的小财管家思考折叠气泡中逐字展示当前业务任务、已识别信息、待补充条件和下一步动作;拿到申请核对表或报销核对结果后,再逐字输出正文。结构化表格、核对卡片、确认按钮可以在正文输出完成后一次性展示,但正文不能一次性替换进消息气泡。
|
||||
|
||||
小财管家委派期间不得打开右侧单助手执行流程面板,也不得把“申请助手 / 报销助手”的执行步骤显示成独立助手思考框。用户可见身份保持“小财管家”,具体调用哪个能力只作为小财管家自己的过程摘要,不切换为“财务助手”或单独助手会话。
|
||||
|
||||
### 5. 用户可见结果展示
|
||||
|
||||
小财管家的第一屏最终正文必须采用适中信息量的分段结构:让用户看懂系统理解了哪些财务事项、先后顺序是什么、每一步会交给哪个助手做什么;但不要把任务摘要、置信度、字段缺口和附件判断提前摊开。
|
||||
|
||||
第一屏推荐结构:
|
||||
|
||||
1. `我会这样推进`:说明识别到几个财务事项,以及会逐步处理。
|
||||
2. 顺序列表:说明先做什么、后做什么,每步附一句负责助手和动作边界。
|
||||
3. 确认提示:请用户回复“确定”后开始第一步,并说明具体缺口会在对应步骤里再判断。
|
||||
|
||||
最终正文必须使用 Markdown 块结构渲染,至少包含标题、段落和顺序列表;标题与段落之间必须保留空行,并通过 `steward-plan-markdown` 专属样式拉开块间距。不能只依赖普通换行拼接文本,因为普通换行在对话气泡里会显得拥挤。
|
||||
|
||||
第一屏不展示任务详情卡片里的“还需要补充”,也不展示字段缺口说明。用户确认开始后,进入当前步骤的申请助手或报销助手,再由具体助手基于当前任务判断需要补充什么。
|
||||
|
||||
后续步骤如果需要展示“还需要补充”,必须是结构化列表,每个待补充项独立成行,包含字段业务名称和填写说明;不得把多个待补充项拼接成一行连续文本。
|
||||
|
||||
当后续步骤发现关键条件缺失时,小财管家不能只展示“模型复核不稳定”或“下方表格待补充”。它必须把缺口转成下一轮对话问题,并优先给出可直接选择的业务选项。例如差旅申请缺少 `transport_mode` 时,用户界面展示为“请问你打算怎么出行?火车、飞机或轮船”,不得先展示申请核对表,也不得默认补成火车;用户选择后再生成申请核对表、写回出行方式、重新测算费用,并继续判断是否可以提交申请。这是“思考 -> 行动 -> 再思考 -> 再行动”循环的一部分。
|
||||
|
||||
用户补齐关键字段也不是终态动作。以“出行方式”为例,用户选择火车后,小财管家必须先进入下一轮业务思考,基于已识别的时间、地点、事由和出行方式模拟查询交通票据或票价口径,完成系统预估金额测算,再流式输出正文并展示申请核对表;不能在用户点击选项后直接把旧核对表补字段后闪现出来。
|
||||
|
||||
费用申请核对表阶段不得把系统档案字段或非阻塞归档字段当作用户待补充项。`employee_no`、`employee_name`、`department_name` 应从当前登录用户档案和组织上下文读取;`attachments` 在差旅申请阶段不阻塞核对表生成,可在后续报销、归档或审批材料补充阶段处理;`amount` 在申请阶段由系统规则估算。字段决策模型即使返回这些字段为缺失,服务端也必须过滤,不能向用户展示“附件/凭证和员工编号为合规必需字段”这类错误追问。
|
||||
|
||||
任务卡片和正文不得直接暴露本体字段名,例如 `transport_mode`、`amount`、`attachments`。本体字段只允许作为内部结构化数据进入后端、助手委派和持久化链路;用户界面必须翻译为业务中文,并提供可理解的填写说明:
|
||||
|
||||
- `transport_mode` 展示为“出行方式”,说明可填写高铁、飞机、自驾、出租车等。
|
||||
- `amount` 在申请任务中展示为“预计金额”,在报销任务中展示为“报销金额”。
|
||||
- `attachments` 展示为“附件/凭证”,说明可上传发票、行程单、付款截图或其他证明材料。
|
||||
- `merchant_name` 展示为“商户/开票方”。
|
||||
- `customer_name` 展示为“客户或项目对象”。
|
||||
|
||||
## 本体字段约束
|
||||
|
||||
业务字段必须使用本体 canonical field:
|
||||
|
||||
- `expense_type`
|
||||
- `time_range`
|
||||
- `location`
|
||||
- `reason`
|
||||
- `amount`
|
||||
- `transport_mode`
|
||||
- `attachments`
|
||||
- `customer_name`
|
||||
- `merchant_name`
|
||||
- `department_name`
|
||||
- `employee_name`
|
||||
- `employee_no`
|
||||
|
||||
兼容字段只能作为输入别名,例如:
|
||||
|
||||
- `occurred_date` -> `time_range`
|
||||
- `business_time` -> `time_range`
|
||||
- `reason_value` -> `reason`
|
||||
- `transport_type` -> `transport_mode`
|
||||
- `application_transport_mode` -> `transport_mode`
|
||||
|
||||
小财管家的编排态字段不进入业务语义本体:
|
||||
|
||||
- `plan_id`
|
||||
- `task_id`
|
||||
- `planning_source`
|
||||
- `model_call_traces`
|
||||
- `task_status`
|
||||
- `assigned_agent`
|
||||
- `confirmation_status`
|
||||
- `attachment_group_id`
|
||||
- `thinking_event_id`
|
||||
|
||||
这些字段只用于编排、展示和审计,不参与费用规则判断。
|
||||
|
||||
## 方案设计
|
||||
|
||||
### 后端
|
||||
|
||||
新增小财管家规划服务:
|
||||
|
||||
- `schemas/steward.py`:定义请求、任务计划、附件归集、确认动作等契约。
|
||||
- `services/runtime_chat.py`:新增 `complete_with_tool_call`,复用主/备模型配置发送 `tools` 与 `tool_choice`。
|
||||
- `services/steward_intent_agent.py`:负责构造 `submit_steward_intent_plan` function schema 与模型调用。
|
||||
- `services/steward_model_plan_builder.py`:负责把模型工具参数转换为服务端可校验计划。
|
||||
- `services/steward_planner.py`:负责“大模型 function calling 优先、规则兜底”的编排和本体字段归一化。
|
||||
- `api/v1/endpoints/steward.py`:提供 `POST /steward/plans` 和 `POST /steward/plans/stream`。
|
||||
|
||||
后端第一版不直接落库业务单据,只返回计划和确认动作。确认后的执行仍走现有申请助手、报销助手和 Orchestrator。
|
||||
|
||||
### 前端
|
||||
|
||||
新增或改造能力:
|
||||
|
||||
- 首页输入框标题和提示文案改为“小财管家”。
|
||||
- 工作台打开时默认使用 `sessionType=steward`。
|
||||
- 小财管家模式下隐藏“智能体切换”工具条。
|
||||
- 小财管家模式下不展示欢迎界面。
|
||||
- 小财管家模式下使用专属底部输入框,仅保留附件、自然语言输入和发送动作。
|
||||
- 小财管家模式下先流式渲染独立过程摘要,再渲染任务计划正文。
|
||||
- 用户确认当前下一步后,再切换/分派到申请助手或报销助手执行;多任务按顺序推进,不把所有任务动作一次性展示给用户。
|
||||
|
||||
### 执行流
|
||||
|
||||
```text
|
||||
首页输入
|
||||
↓
|
||||
小财管家计划接口
|
||||
↓
|
||||
意图识别智能体 function calling
|
||||
↓
|
||||
思考过程流式输出 + 任务分析 + 下一步动作说明
|
||||
↓
|
||||
等待用户输入“确定”或点击确认
|
||||
↓
|
||||
小财管家隐式调用申请助手创建申请单核对结果
|
||||
↓
|
||||
申请动作完成后重新思考剩余队列
|
||||
↓
|
||||
继续等待确认并隐式调用报销助手填写报销单
|
||||
↓
|
||||
执行结果汇总
|
||||
```
|
||||
|
||||
## 算法与公式
|
||||
|
||||
第一版主路径不以关键词规则定义“意图”,而是使用大模型 function calling 生成结构化计划。
|
||||
模型输出后由服务端做确定性校验、字段归一化和确认动作生成。
|
||||
|
||||
规则置信度评分仅用于模型不可用或模型返回结构不可用时的兜底路径。
|
||||
|
||||
任务拆解之后还需要第二层“任务字段决策智能体”。这一步不能由前端关键词或固定 required 字段直接决定,而要把当前任务类型、用户原话、上游任务拆解结果、canonical ontology fields、已抽取字段、缺失字段、附件和申请/报销上下文交给模型,通过 function calling 返回下一步动作:
|
||||
|
||||
- `ask_user`:当前信息不足,必须先把缺口转成业务问题和可选项。
|
||||
- `render_preview`:当前信息足够生成可核对结果,但提交、入库、绑定附件前仍需用户确认。
|
||||
|
||||
字段决策规则只能作为模型不可用或结构化结果非法时的兜底,兜底结果必须标记为 `rule_fallback`,不能伪装成智能体判断。字段名必须来自 ontology registry;UI 只展示中文业务名称,不展示 canonical 字段名。
|
||||
|
||||
任务置信度:
|
||||
|
||||
$$
|
||||
confidence = \min(1, 0.35s_i + 0.25s_t + 0.2s_l + 0.2s_a)
|
||||
$$
|
||||
|
||||
变量说明:
|
||||
|
||||
- `s_i`:意图关键词得分,命中申请/报销核心动词。
|
||||
- `s_t`:时间得分,识别到明确日期、相对日期或时间范围。
|
||||
- `s_l`:地点得分,识别到城市、客户或业务对象。
|
||||
- `s_a`:附件/费用场景得分,识别到票据、交通、住宿、招待等费用线索。
|
||||
|
||||
附件归集置信度:
|
||||
|
||||
$$
|
||||
group\_score = 0.4m_s + 0.3m_t + 0.2m_l + 0.1m_n
|
||||
$$
|
||||
|
||||
变量说明:
|
||||
|
||||
- `m_s`:附件场景与任务场景匹配度。
|
||||
- `m_t`:附件日期与任务日期匹配度。
|
||||
- `m_l`:附件地点与任务地点匹配度。
|
||||
- `m_n`:附件名称和任务关键词匹配度。
|
||||
|
||||
## 测试方案
|
||||
|
||||
### 后端单元测试
|
||||
|
||||
- function calling 路径能把模型工具参数转换为 `planning_source=llm_function_call` 的任务计划。
|
||||
- 模型返回 `occurred_date`、`transport_type`、`reason_value` 等别名时,服务端仍只输出 canonical 字段。
|
||||
- 一句话中同时包含申请和报销时,返回多个任务。
|
||||
- “昨天”能根据 `client_now_iso` 解析为明确日期。
|
||||
- `occurred_date`、`transport_type`、`reason_value` 等兼容字段不会作为业务 canonical 字段输出。
|
||||
- 多附件能生成差旅归集建议和排除项。
|
||||
- 创建/绑定/提交类动作必须带 `confirmation_required=true`。
|
||||
|
||||
### 前端测试
|
||||
|
||||
- 首页输入复杂话术后打开小财管家模式。
|
||||
- 小财管家模式标题显示“小财管家”,不展示智能体切换。
|
||||
- 过程摘要按步骤渐进展示。
|
||||
- 任务计划卡片展示申请任务和报销任务。
|
||||
- 附件归集建议展示包含项、排除项和确认按钮。
|
||||
|
||||
### 容器验证
|
||||
|
||||
后端测试必须在 `x-financial-main` 容器内执行:
|
||||
|
||||
```bash
|
||||
docker exec -w /app -e SERVER_VENV_DIR=/tmp/x-financial-server-venv x-financial-main /tmp/x-financial-server-venv/bin/pytest -q server/tests/test_steward_planner.py
|
||||
```
|
||||
|
||||
前端构建优先使用宿主机 `npm.cmd` 或项目既有脚本,并设置合理超时。
|
||||
|
||||
## 指标与验收
|
||||
|
||||
- 输入包含 3 个任务的示例话术时,至少识别出 1 个申请任务和 2 个报销任务。
|
||||
- 输入“明天出差北京3天,支撑国网仿生产部署,并且报销昨天业务招待费”时,必须识别出 1 个申请任务和 1 个报销任务。
|
||||
- 模型可用时,小财管家计划响应包含 `planning_source=llm_function_call`。
|
||||
- 小财管家计划响应中业务字段只出现 canonical ontology fields。
|
||||
- 附件场景混合时,能区分差旅相关附件和非差旅附件。
|
||||
- 前端弹窗标题为“小财管家”,并隐藏智能体切换。
|
||||
- 前端确认区只展示当前下一步主动作;存在申请任务时,第一步必须是“先创建申请单”。
|
||||
- 意图识别折叠气泡不得宽于正文气泡,且流式首包必须先于最终计划到达。
|
||||
- 用户未确认前,不创建申请单、不创建报销草稿、不绑定附件、不提交审批。
|
||||
- 后端定向测试通过。
|
||||
|
||||
## 风险与开放问题
|
||||
|
||||
- 模型供应商对 tools/function calling 的兼容度可能不同;第一版保留规则兜底和主备模型 failover。
|
||||
- 规则兜底无法覆盖所有自然语言,需要保留人工确认和低置信度提示。
|
||||
- 附件真实 OCR 归集依赖现有票据识别质量;第一版先使用附件名称和已有 OCR 摘要做轻量归集。
|
||||
- NDJSON 流式输出展示的是过程摘要,不是模型内部推理链。
|
||||
- 多任务之间可能共享日期、地点、申请单上下文,需要后续完善任务图依赖。
|
||||
- 如果未来接入 LangGraph,应基于当前计划契约迁移,而不是推翻现有申请/报销助手。
|
||||
@@ -1,66 +0,0 @@
|
||||
# 小财管家 TODO
|
||||
|
||||
## 阶段一:调研与契约
|
||||
|
||||
- [x] 盘点首页输入框、工作台弹窗、会话路由和本体字段注册表。[CONCEPT: 背景与问题] 证据:已确认 `PersonalWorkbench.vue`、`useAppShell.js`、`TravelReimbursementCreateView.vue`、`ontology_field_registry.py`。
|
||||
- [x] 定义第一版只覆盖申请助手和报销助手,不引入 LangChain,但外层意图识别使用大模型 function calling。[CONCEPT: 目标与非目标] 证据:`CONCEPT.md` 已明确 MVP 边界和 function calling 主链路。
|
||||
- [x] 明确小财管家业务字段必须走 ontology canonical fields,编排字段不得进入业务本体。[CONCEPT: 本体字段约束] 证据:`CONCEPT.md` 已列出 canonical 字段与编排态字段。
|
||||
|
||||
## 阶段二:后端规划服务
|
||||
|
||||
- [x] 新增 `schemas/steward.py`,定义计划请求、任务、附件归集、确认动作和响应模型。[CONCEPT: 后端] 证据:`StewardPlanRequest`、`StewardTask`、`StewardAttachmentGroup`、`StewardConfirmationAction` 已新增。
|
||||
- [x] 扩展 `services/runtime_chat.py`,支持 OpenAI-compatible / Azure OpenAI 的 `tools` 与 `tool_choice` function calling。[CONCEPT: 后端] 证据:新增 `complete_with_tool_call`、`RuntimeChatToolCall` 和工具调用解析。
|
||||
- [x] 新增 `services/steward_intent_agent.py`,定义 `submit_steward_intent_plan` function schema 并调用系统主/备模型。[CONCEPT: 任务识别与拆分] 证据:模型调用入口已从 `StewardPlannerService` 注入。
|
||||
- [x] 新增 `services/steward_model_plan_builder.py`,将模型工具参数转换为服务端可校验计划。[CONCEPT: 后端] 证据:模型返回后仍会校验任务类型、canonical 字段和附件名。
|
||||
- [x] 改造 `services/steward_planner.py`,实现大模型 function calling 优先、规则规划兜底。[CONCEPT: 任务识别与拆分] 证据:`planning_source` 区分 `llm_function_call` 与 `rule_fallback`。
|
||||
- [x] 新增 `api/v1/endpoints/steward.py`,提供 `POST /steward/plans`。[CONCEPT: 后端] 证据:容器内接口 smoke 返回 `task_count=3`。
|
||||
- [x] 新增 `POST /steward/plans/stream`,以 NDJSON 返回 `thinking` 和最终 `plan` 事件。[CONCEPT: 流式过程摘要] 证据:真实接口 smoke 返回事件序列 `thinking,thinking,thinking,thinking,plan`。
|
||||
- [x] 调整 `POST /steward/plans/stream`,确保模型 function calling 完成前先返回首个 `thinking` 事件。[CONCEPT: 流式过程摘要] 证据:live smoke 首个事件为 `thinking/stream_start`。
|
||||
- [x] 在 `api/v1/router.py` 注册小财管家接口。[CONCEPT: 后端] 证据:运行中 `/api/v1/steward/plans` 返回 200。
|
||||
- [x] 保证所有输出到 `ontology_fields` 的业务字段只使用 canonical ontology fields。[CONCEPT: 本体字段约束] 证据:测试断言 `occurred_date`、`transport_type`、`reason_value` 不进入输出字段。
|
||||
- [x] 强化模型提示词和规则兜底,确保“未来出差/去某地几天/部署支撑”即使未出现“申请”也识别为费用申请。[CONCEPT: 任务识别与拆分] 证据:live smoke 将“明天出差北京3天...”拆为 `expense_application,reimbursement`。
|
||||
|
||||
## 阶段三:前端入口和弹窗
|
||||
|
||||
- [x] 将首页输入区主文案调整为“小财管家”,提示语体现可处理多任务。[CONCEPT: 前端] 证据:`PersonalWorkbench.vue` 标题和 placeholder 已更新。
|
||||
- [x] 增加 `steward` 会话类型,首页复杂输入默认进入小财管家模式。[CONCEPT: 前端] 证据:`SESSION_TYPE_STEWARD` 与首页 `sessionType` 已接入。
|
||||
- [x] 小财管家模式下隐藏“智能体切换”工具条。[CONCEPT: 前端] 证据:`shortcuts` 在 `isStewardSession` 下返回空数组。
|
||||
- [x] 小财管家模式下标题显示“小财管家”,副标题说明“统一财务任务编排入口”。[CONCEPT: 前端] 证据:`assistantHeaderTitle` 和 `assistantHeaderDescription` 已按 steward 分支处理。
|
||||
- [x] 小财管家模式下不展示欢迎界面。[CONCEPT: 前端] 证据:`useTravelReimbursementSessionState.js` 对 steward 空会话返回空消息,并过滤旧欢迎消息快照。
|
||||
- [x] 小财管家模式下使用专属底部输入框,不展示日期选择、差旅计算器和业务时间标签。[CONCEPT: 前端] 证据:`TravelReimbursementCreateView.vue` 按 `isStewardSession` 渲染 `steward-composer-row`。
|
||||
- [x] 新增前端小财管家计划服务,调用 `POST /steward/plans`。[CONCEPT: 后端] 证据:`web/src/services/steward.js` 已新增 `fetchStewardPlan`。
|
||||
- [x] 新增小财管家视图模型,生成过程摘要、任务计划卡片和附件归集卡片。[CONCEPT: 流式过程摘要] 证据:`stewardPlanModel.js` 和 `TravelReimbursementMessageItem.vue` 已接入 `stewardPlan`。
|
||||
- [x] 支持后端 `thinking` 事件真流式呈现为折叠式意图识别气泡。[CONCEPT: 流式过程摘要] 证据:`useStewardPlanFlow.js` 通过 `fetchStewardPlanStream` 接收 thinking 事件,并用 `typeStewardThinkingEvent` 将每条过程摘要逐字输出到折叠气泡。
|
||||
- [x] 支持小财管家最终正文逐字流式输出,正文完成前不展示确认按钮。[CONCEPT: 流式过程摘要] 证据:`useStewardPlanFlow.js` 新增 `typeStewardPlanText`,计划完成后进入 `typing` 状态逐字追加正文,完成后再注入 `suggestedActions`。
|
||||
- [x] 意图识别过程放在系统回复气泡上方,作为不同颜色的独立折叠气泡,完成后默认折叠。[CONCEPT: 流式过程摘要] 证据:`TravelReimbursementMessageItem.vue` 将 `steward-intent-bubble` 放在 `message-bubble` 上方,`steward-plan-block` 只渲染任务和附件结果。
|
||||
- [x] 统一小财管家思考折叠气泡与正文气泡宽度,避免思考气泡长于正文框。[CONCEPT: 流式过程摘要] 证据:`has-steward-plan` 消息栈统一为 760px,思考气泡和正文气泡同宽。
|
||||
- [x] 优化小财管家最终正文和任务卡片层次,用户可见内容不直接展示本体字段名。[CONCEPT: 用户可见结果展示] 证据:`stewardPlanModel.js` 第一屏使用 Markdown 标题、段落和顺序列表说明“先做什么、后做什么、交给哪个助手做什么”,但不展示置信度和字段缺口;`useStewardPlanFlow.js` 将第一屏标记为 `initialSummaryOnly`,`TravelReimbursementMessageItem.vue` 不再渲染第一屏任务详情卡片;后续步骤如需展示待补充项,再按独立列表行展示业务名称和填写说明。
|
||||
|
||||
## 阶段四:确认与分派
|
||||
|
||||
- [x] 为每个创建/绑定/提交类动作生成确认按钮,不确认不执行。[CONCEPT: 用户确认] 证据:接口返回 `confirmation_count=4`,前端转为 suggested actions。
|
||||
- [x] 将小财管家确认区改为“只展示当前下一步主动作”,存在申请任务时优先进入申请助手。[CONCEPT: 用户确认] 证据:`buildStewardSuggestedActions` 只返回下一步动作,优先 `confirm_create_application`。
|
||||
- [x] 支持用户输入“确定”触发小财管家的下一步动作,而不是重新生成计划。[CONCEPT: 用户确认] 证据:`useStewardPlanFlow` 会查找待确认的小财管家动作并执行。
|
||||
- [x] 支持小财管家隐藏委派申请/报销能力,执行后保留小财管家会话并继续引导剩余任务。[CONCEPT: 执行流] 证据:`sessionTypeOverride` 和 `stewardContinuation` 已接入前端提交流程。
|
||||
- [x] 支持小财管家确认后的隐式委派继续流式输出,正文完成后再展示申请核对表、报销核对卡片和确认按钮。[CONCEPT: 流式过程摘要] 证据:`useTravelReimbursementSubmitComposer.js` 新增 `typeStewardDelegatedMessage`,申请预览与 orchestrator 结果均先流式思考、再逐字正文、最后挂载结构化 payload;`npm.cmd --prefix web run build` 成功。
|
||||
- [x] 小财管家委派申请/报销能力期间不打开右侧单助手执行流程面板,用户可见身份保持“小财管家”。[CONCEPT: 流式过程摘要] 证据:`stewardDelegated` 分支跳过 flow step 与 review panel scope,并在最终消息设置 `assistantName: '小财管家'`;`stewardPlanModel.js` 助手标签兜底不再显示“财务助手”。
|
||||
- [x] 小财管家在后续步骤发现关键缺口时,主动转成可回答的问题和选项,而不是只展示待补充表格。[CONCEPT: 用户可见结果展示] 证据:`useTravelReimbursementSubmitComposer.js` 在申请核对缺少“出行方式”时只输出主动追问和火车/飞机/轮船选项,不提前挂载 `applicationPreview`;`stewardPlanModel.js` 的内部 `carry_text` 不再把“高铁、飞机”等示例写进缺字段提示,避免本地抽取误当成用户已选择;`TravelReimbursementCreateView.js` 在用户选择后不再直接补旧表格,而是重新进入小财管家的委派流;`web/tests/expense-application-fast-preview.test.mjs` 覆盖该回归。
|
||||
- [x] 用户补齐出行方式后,小财管家必须先思考、模拟查询票据和测算金额,再展示申请核对表。[CONCEPT: 用户可见结果展示] 证据:`stewardFieldCompletionModel.js` 将补齐字段后的当前任务、本体字段和旧预览重组为续跑输入;`TravelReimbursementCreateView.js` 的 `continueStewardApplicationFieldCompletion` 调用 `submitComposerInternal` 触发流式思考、申请复核和费用测算,不再调用 `commitApplicationPreviewEditor` 直接闪现表格。
|
||||
- [x] 防止残留预算上下文抢占小财管家的申请续跑链路。[CONCEPT: 执行流] 证据:`budgetAssistantReportModel.js` 不再因存在 `initialBudgetContext` 就无条件进入预算编制报告;`useTravelReimbursementSubmitComposer.js` 对 `stewardDelegated` 显式跳过预算编制分支;`expense-application-fast-preview.test.mjs` 覆盖“申请续跑 + 残留预算上下文”不得进入预算编制。
|
||||
- [x] 支持用户直接输入“确认/无误/可以提交”命中当前申请核对表提交动作,而不是重新规划。[CONCEPT: 用户确认] 证据:`TravelReimbursementCreateView.js` 通过 `handleStewardRuntimeDecision` 优先请求运行时决策智能体;模型返回 `submit_current_application` 后复用 `confirmApplicationSubmit`;本地 `handleApplicationSubmitConfirmationText` 仅作为模型不可用时的兜底;提交成功后标记 `applicationSubmitConfirmed`,避免后续重复提交;测试 `text confirmation submits pending application preview before replanning steward task` 覆盖该优先级。
|
||||
- [x] 增加小财管家运行时决策智能体,把“确认、继续下一项、补字段、重新规划”的上下文判断迁到后端 function calling。[CONCEPT: 用户确认] 证据:`POST /steward/runtime-decisions` 调用 `StewardRuntimeDecisionAgent`,通过 `submit_steward_runtime_decision` 返回 `submit_current_application`、`continue_next_task`、`fill_current_slot`、`plan_new_tasks` 等动作;前端 `handleStewardRuntimeDecision` 先提交 `runtime_state`,再执行模型返回的结构化动作,本地规则仅兜底。
|
||||
- [x] 增加第二层任务字段决策智能体,动态判断当前任务应追问用户还是展示核对结果。[CONCEPT: 算法与公式] 证据:`POST /steward/slot-decisions` 调用 `StewardSlotDecisionAgent`,通过 `submit_steward_slot_decision` function calling 输出 `ask_user` / `render_preview`、canonical 缺失字段、问题和选项;前端 `useTravelReimbursementSubmitComposer.js` 在小财管家委派申请时消费该决策。
|
||||
- [x] 防止字段决策模型把申请阶段非阻塞字段误判为用户必填项。[CONCEPT: 用户可见结果展示] 证据:`StewardSlotDecisionAgent` 过滤 `amount`、`attachments`、`employee_no`、`department_name`、`employee_name`,模型误返 `ask_user` 且过滤后无缺口时改为 `render_preview`;前端 `APPLICATION_NON_BLOCKING_ONTOLOGY_FIELDS` 同步过滤兜底缺口和选项;测试覆盖附件/员工编号误判。
|
||||
- [x] 小财管家思考气泡必须体现业务意图和关键缺口,不能退化为系统执行日志。[CONCEPT: 流式过程摘要] 证据:`steward_planner.py` 将差旅申请缺少“出行方式”纳入计划缺口并追加业务缺口思考事件;`useTravelReimbursementSubmitComposer.js` 和 `TravelReimbursementCreateView.js` 的确认后思考改为读取任务摘要、已识别信息和待补充项。
|
||||
- [x] 确认申请任务后,将任务摘要分派到现有申请助手会话。[CONCEPT: 执行流] 证据:确认动作携带 `session_type=application` 和 `auto_submit=true`。
|
||||
- [x] 确认报销任务后,将任务摘要和附件带入现有报销助手会话。[CONCEPT: 执行流] 证据:确认动作携带 `session_type=expense`、`carry_files=true` 和 `auto_submit=true`。
|
||||
- [x] 附件归集建议确认后,将选中附件作为报销助手上下文继续处理。[CONCEPT: 附件归集] 证据:附件归集确认动作携带归集附件名称和排除附件名称。
|
||||
|
||||
## 阶段五:测试与验证
|
||||
|
||||
- [x] 新增 `server/tests/test_steward_planner.py`,覆盖多任务拆解、相对日期、附件归集、确认动作和流式事件。[CONCEPT: 测试方案] 证据:新增 4 个后端定向测试。
|
||||
- [x] 补充 function calling 定向测试,覆盖模型工具参数、canonical 字段清洗、附件归集和规则兜底。[CONCEPT: 后端单元测试] 证据:`test_steward_planner.py` 新增 fake function calling 路径,`test_runtime_chat_service.py` 新增 tools payload 用例。
|
||||
- [x] 后端测试在 Docker `x-financial-main:/app` 内执行,超时控制在 60s 内。[CONCEPT: 容器验证] 证据:`pytest -q server/tests/test_steward_planner.py server/tests/test_runtime_chat_service.py` 结果 `13 passed`。
|
||||
- [ ] 新增或更新前端定向测试,覆盖小财管家标题、隐藏智能体切换和计划展示。[CONCEPT: 前端测试]
|
||||
- [x] 运行前端构建或定向测试,确认 UI 编译通过。[CONCEPT: 测试方案] 证据:`npm.cmd run build` 成功。
|
||||
- [x] 通过接口或页面可见结果证明用户最终看到小财管家计划和确认点。[CONCEPT: 指标与验收] 证据:容器接口返回 3 个任务、3 份归集附件、1 份排除附件和 4 个确认动作。
|
||||
@@ -1,273 +0,0 @@
|
||||
# 小财管家本体 JSON 流程
|
||||
|
||||
## 功能一句话
|
||||
|
||||
用大模型作为小财管家的主意图识别器,将用户连续对话转换为受本体字段约束的业务 JSON,并在申请和报销意图不确定时先进入用户确认,而不是用固定规则直接判定。
|
||||
|
||||
## 背景与问题
|
||||
|
||||
当前小财管家已经具备任务规划、部分运行时状态和申请/报销委派能力,但仍有两个关键缺口:
|
||||
|
||||
- 意图识别仍带有较强规则假设。例如“2月20-23日去上海出差辅助国网仿生产环境部署”这类话术,在没有“申请”或“报销”动词时,系统不能仅凭规则直接判定为申请。
|
||||
- 跨轮对话需要一个贯穿流程的结构化 JSON。该 JSON 必须只承载本体 canonical field,不能由前端、规则或大模型临时发明业务字段。
|
||||
|
||||
因此,本轮目标不是重写整个小财管家,而是在现有 `steward` 体系上补齐“LLM 主识别 + 本体 JSON 模板 + 待确认流程 + 上下文记忆”的闭环。
|
||||
|
||||
## 目标与非目标
|
||||
|
||||
### 目标
|
||||
|
||||
- 用大模型 function calling 作为主路径识别用户意图。
|
||||
- 模型输出必须落到统一业务 JSON 模板,字段来源必须来自本体字段注册表。
|
||||
- 支持 `travel_application` 和 `travel_reimbursement` 两个业务流程。
|
||||
- 当用户话术无法确定是申请还是报销时,返回 `pending_flow_confirmation`,由前端展示两个明确选项。
|
||||
- 跨轮对话持续携带并合并 `steward_state`,直到用户完成、取消或切换业务。
|
||||
- 规则只做兜底,且响应必须标记 `rule_fallback`,不能伪装成模型判断。
|
||||
- 用户可见回复使用 Markdown 块结构,重点信息加粗,避免密集换行。
|
||||
|
||||
### 非目标
|
||||
|
||||
- 本轮不引入 LangChain 或 LangGraph。
|
||||
- 本轮不迁移申请助手、报销助手和 Orchestrator 的既有核心逻辑。
|
||||
- 本轮不让大模型直接创建申请单、保存草稿、绑定附件或提交审批。
|
||||
- 本轮不新增脱离本体字段体系的新业务字段。
|
||||
- 本轮不改造所有财务场景,只先覆盖出差申请和差旅/费用报销。
|
||||
|
||||
## 用户与场景
|
||||
|
||||
- 普通员工:在首页或小财管家对话框中说“2月20-23日去上海出差辅助国网仿生产环境部署”。
|
||||
- 小财管家:先判断该话术包含出差、时间、地点和事由,但缺少“申请还是报销”的明确动作。
|
||||
- 用户:点击“补办出差申请”或“发起费用报销”。
|
||||
- 系统:将用户选择写入同一个业务 JSON,并继续用对应流程追问缺字段、生成核对结果或委派现有助手。
|
||||
|
||||
示例预期:
|
||||
|
||||
```markdown
|
||||
我识别到你描述的是一次 **上海出差事项**,时间为 **2月20日至2月23日**,事由是 **辅助国网仿生产环境部署**。
|
||||
|
||||
但当前还不能确定你要做哪一件事:
|
||||
|
||||
1. **补办出差申请**
|
||||
2. **发起费用报销**
|
||||
|
||||
请先选择一个方向,我会继续整理对应材料。
|
||||
```
|
||||
|
||||
## 功能能力
|
||||
|
||||
### 输入
|
||||
|
||||
- 用户自然语言 `message`。
|
||||
- 当前时间 `client_now_iso`,用于解析相对日期。
|
||||
- 附件元信息和 OCR 摘要。
|
||||
- 当前 `conversation_id`。
|
||||
- 已持久化 `steward_state`。
|
||||
- ontology canonical fields 列表。
|
||||
|
||||
### 输出
|
||||
|
||||
- `steward_state`:贯穿对话的业务 JSON。
|
||||
- `intent_result`:本轮模型或兜底规则的识别结果。
|
||||
- `candidate_flows`:存在歧义时的候选流程。
|
||||
- `next_action`:下一步动作,例如追问、确认流程、渲染申请预览、渲染报销预审。
|
||||
- `markdown_reply`:面向用户的 Markdown 回复。
|
||||
|
||||
### 状态边界
|
||||
|
||||
业务 JSON 必须区分业务字段和编排字段:
|
||||
|
||||
- 业务字段只允许出现在 `flows.<flow_id>.fields`。
|
||||
- 业务字段 key 必须是 canonical ontology field。
|
||||
- 编排字段只能出现在 `active_flow`、`pending_flow_confirmation`、`events`、`status` 等结构里。
|
||||
- 规则或模型返回的别名字段必须先归一化,例如 `occurred_date -> time_range`、`transport_type -> transport_mode`、`reason_value -> reason`。
|
||||
|
||||
### 安全边界
|
||||
|
||||
- 保存草稿、创建申请单、提交审批、删除或绑定附件必须等待用户确认。
|
||||
- LLM 只能产出结构化建议,不直接执行副作用操作。
|
||||
- 如果模型返回非法字段、非法流程或非法动作,服务端丢弃非法部分并进入保守兜底。
|
||||
|
||||
## 业务 JSON 模板
|
||||
|
||||
目标模板如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "steward.flow_state.v2",
|
||||
"active_flow": "",
|
||||
"pending_flow_confirmation": {
|
||||
"status": "none",
|
||||
"source_message": "",
|
||||
"reason": "",
|
||||
"candidate_flows": []
|
||||
},
|
||||
"flows": {
|
||||
"travel_application": {
|
||||
"flow_id": "travel_application",
|
||||
"intent": "travel_application_create",
|
||||
"status": "idle",
|
||||
"fields": {},
|
||||
"missing_fields": [],
|
||||
"confidence": 0,
|
||||
"evidence": []
|
||||
},
|
||||
"travel_reimbursement": {
|
||||
"flow_id": "travel_reimbursement",
|
||||
"intent": "travel_reimbursement_draft",
|
||||
"status": "idle",
|
||||
"fields": {},
|
||||
"missing_fields": [],
|
||||
"linked_application_claim_id": "",
|
||||
"attachments": [],
|
||||
"confidence": 0,
|
||||
"evidence": []
|
||||
}
|
||||
},
|
||||
"events": []
|
||||
}
|
||||
```
|
||||
|
||||
候选流程结构:
|
||||
|
||||
```json
|
||||
{
|
||||
"flow_id": "travel_application",
|
||||
"label": "补办出差申请",
|
||||
"confidence": 0.52,
|
||||
"reason": "用户描述了出差时间、地点和事由,但没有明确要求报销或提交申请。"
|
||||
}
|
||||
```
|
||||
|
||||
## 方案设计
|
||||
|
||||
### 后端
|
||||
|
||||
新增或扩展以下职责:
|
||||
|
||||
- `schemas/steward.py`:增加 v2 JSON 状态、候选流程、待确认流程和意图识别响应模型。
|
||||
- `services/steward_intent_agent.py`:扩展 function schema,允许模型返回 `pending_flow_confirmation` 和 `candidate_flows`。
|
||||
- `services/steward_model_plan_builder.py`:校验模型输出,只保留合法 flow、合法 action 和 canonical ontology fields。
|
||||
- `services/steward_flow_state.py`:支持 v1 到 v2 状态兼容、字段 patch 合并、候选流程落态和事件追踪。
|
||||
- `services/steward_runtime_decision_agent.py`:识别用户点击或输入的流程选择,并把选择写回 `active_flow`。
|
||||
- `api/v1/endpoints/steward.py`:在 `/steward/plans`、`/steward/plans/stream`、`/steward/runtime-decisions` 中统一返回最新 `steward_state`。
|
||||
|
||||
### 前端
|
||||
|
||||
- `stewardPlanModel.js`:将 `pending_flow_confirmation` 转为可点击操作。
|
||||
- `TravelReimbursementCreateView.js`:用户点击候选流程后,优先走 runtime decision,不重新把原句当新任务规划。
|
||||
- `useStewardPlanFlow.js`:渲染 Markdown 回复和候选流程操作。
|
||||
- `useTravelReimbursementSessionState.js`:持续保存并传回 `conversation_id` 和 `steward_state`。
|
||||
|
||||
### 数据与持久化
|
||||
|
||||
- 复用 `AgentConversation.state_json` 持久化 `steward_state`。
|
||||
- 不新增数据库表。
|
||||
- 不改变申请单、报销单现有表结构。
|
||||
|
||||
### 接口契约
|
||||
|
||||
`POST /steward/plans` 和流式计划接口返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"planning_source": "llm_function_call",
|
||||
"conversation_id": "conv_xxx",
|
||||
"steward_state": {},
|
||||
"next_action": "confirm_flow",
|
||||
"candidate_flows": [],
|
||||
"summary": "Markdown 文本"
|
||||
}
|
||||
```
|
||||
|
||||
运行时确认接口返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"decision_source": "llm_function_call",
|
||||
"next_action": "continue_selected_flow",
|
||||
"steward_state": {},
|
||||
"response_text": "Markdown 文本"
|
||||
}
|
||||
```
|
||||
|
||||
## 算法与公式
|
||||
|
||||
主路径不使用关键词打分决定最终意图,而是由 LLM function calling 返回结构化候选结果。
|
||||
|
||||
规则兜底仅在模型不可用、超时或结构非法时使用。兜底置信度用于决定是否直接进入候选确认:
|
||||
|
||||
$$
|
||||
confidence(flow) = 0.35t + 0.25l + 0.25v + 0.15a
|
||||
$$
|
||||
|
||||
变量定义:
|
||||
|
||||
- `t`:时间线索得分,出现明确日期、日期区间或相对日期时取 1,否则取 0。
|
||||
- `l`:地点线索得分,出现城市、客户地点或项目地点时取 1,否则取 0。
|
||||
- `v`:动作线索得分,出现申请、报销、提交、保存草稿等动作词时取 1,否则取 0。
|
||||
- `a`:附件线索得分,存在票据、发票、行程单、OCR 金额等附件证据时取 1,否则取 0。
|
||||
|
||||
当最高候选流程与第二候选流程差值小于阈值时进入确认:
|
||||
|
||||
$$
|
||||
\Delta = confidence(flow_1) - confidence(flow_2) < 0.20
|
||||
$$
|
||||
|
||||
该公式只用于兜底路径,不能覆盖模型主判断。
|
||||
|
||||
## 测试方案
|
||||
|
||||
### 后端单元测试
|
||||
|
||||
- `test_steward_intent_agent.py`:覆盖 function schema 包含 `candidate_flows`、`pending_flow_confirmation`。
|
||||
- `test_steward_model_plan_builder.py`:覆盖非法字段过滤、别名归一、非法 flow 丢弃。
|
||||
- `test_steward_flow_state.py`:覆盖 v2 状态合并、候选流程落态、用户选择后 active flow 切换。
|
||||
- `test_steward_runtime_decision_agent.py`:覆盖用户选择“补办出差申请 / 发起费用报销”。
|
||||
|
||||
### 接口测试
|
||||
|
||||
- `/steward/plans` 输入“2月20-23日去上海出差辅助国网仿生产环境部署”,返回 `next_action=confirm_flow`。
|
||||
- `/steward/runtime-decisions` 选择“补办出差申请”后,`active_flow=travel_application`。
|
||||
- `/steward/runtime-decisions` 选择“发起费用报销”后,`active_flow=travel_reimbursement`。
|
||||
|
||||
### 前端测试
|
||||
|
||||
- 候选流程按钮只在 `pending_flow_confirmation.status=pending` 时展示。
|
||||
- 用户点击候选流程后不重复触发新计划。
|
||||
- Markdown 回复中标题、段落、列表和重点加粗能正确渲染。
|
||||
|
||||
### 容器验证
|
||||
|
||||
后端测试必须在 Docker 容器内执行:
|
||||
|
||||
```bash
|
||||
docker exec -w /app -e SERVER_VENV_DIR=/tmp/x-financial-server-venv x-financial-main /tmp/x-financial-server-venv/bin/pytest -q server/tests/test_steward_intent_agent.py server/tests/test_steward_model_plan_builder.py server/tests/test_steward_flow_state.py server/tests/test_steward_runtime_decision_agent.py
|
||||
```
|
||||
|
||||
前端构建必须在容器内执行:
|
||||
|
||||
```bash
|
||||
docker exec -w /app/web x-financial-main npm run build
|
||||
```
|
||||
|
||||
单次测试命令最长等待 60 秒,避免任务卡死。
|
||||
|
||||
## 指标与验收
|
||||
|
||||
- 对“2月20-23日去上海出差辅助国网仿生产环境部署”,系统不再直接判定为申请,而是返回两个候选流程并要求用户确认。
|
||||
- 用户选择“补办出差申请”后,同一 `conversation_id` 的 `steward_state.active_flow=travel_application`。
|
||||
- 用户选择“发起费用报销”后,同一 `conversation_id` 的 `steward_state.active_flow=travel_reimbursement`。
|
||||
- `flows.*.fields` 中不出现非本体字段。
|
||||
- 模型返回别名字段时,服务端输出仍为 canonical ontology field。
|
||||
- 模型不可用时,规则兜底结果明确标记 `rule_fallback`。
|
||||
- 用户未确认前,不创建申请单、不保存报销草稿、不提交审批、不绑定附件。
|
||||
- 前端候选流程按钮点击后不产生重复消息、不重复规划、不丢失上下文。
|
||||
- 后端定向测试和前端构建在 `x-financial-main:/app` 通过。
|
||||
|
||||
## 风险与开放问题
|
||||
|
||||
- 模型供应商对 function calling 的兼容程度不同,需要保留严格的服务端结构校验。
|
||||
- 旧版 `steward_state.v1` 已有数据需要兼容升级到 v2。
|
||||
- 用户输入可能同时包含“补申请”和“报销”,这种情况不应进入歧义确认,而应拆成两个任务。
|
||||
- 过去日期不等于报销,未来日期也不绝对等于申请;最终应由 LLM 主识别,并用候选确认处理低确定性场景。
|
||||
- 后续如果要支持更多流程,例如审批、制度问答或预算查询,需要先扩展本体业务契约,再扩展本 JSON 模板。
|
||||
@@ -1,75 +0,0 @@
|
||||
# 小财管家本体 JSON 流程 TODO
|
||||
|
||||
> 开发时必须先更新本 TODO,再按小步执行。只有真实完成并通过对应验证后,才能把 `[ ]` 改成 `[x]` 并补充证据。
|
||||
|
||||
## 阶段一:调研与契约确认
|
||||
|
||||
- [x] 盘点 `schemas/steward.py`、`steward_intent_agent.py`、`steward_model_plan_builder.py`、`steward_flow_state.py` 的当前状态模型。[CONCEPT: 方案设计] 证据:已在实现前读取并确认现有 `steward_state`、planner、runtime decision 入口。
|
||||
- [x] 盘点 `ontology_field_registry.py` 中申请和报销可使用的 canonical ontology fields。[CONCEPT: 业务 JSON 模板] 证据:实现复用 `BUSINESS_CANONICAL_FIELDS` 与 `normalize_ontology_form_values`。
|
||||
- [x] 确认 `AgentConversation.state_json` 中已有 `steward_state.v1` 数据的兼容方式。[CONCEPT: 数据与持久化] 证据:`StewardFlowStateService._normalize_state` 兼容旧 state 并升级默认版本为 `steward.flow_state.v2`。
|
||||
- [x] 复核前端 `stewardPlanModel.js`、`useStewardPlanFlow.js`、`TravelReimbursementCreateView.js` 中候选动作和状态携带入口。[CONCEPT: 前端] 证据:前端子智能体只读检查确认建议动作入口可复用。
|
||||
|
||||
## 阶段二:后端 Schema 与 JSON 模板
|
||||
|
||||
- [x] 在 `schemas/steward.py` 增加 `StewardCandidateFlow`、`StewardPendingFlowConfirmation`、v2 `steward_state` 相关模型。[CONCEPT: 业务 JSON 模板] 证据:新增模型与 `StewardPlanResponse.pending_flow_confirmation`。
|
||||
- [x] 在 `StewardPlanResponse` 和 runtime response 中补充 `next_action`、`candidate_flows` 或等价结构,保持旧字段兼容。[CONCEPT: 接口契约] 证据:`StewardPlanResponse.next_action/candidate_flows` 与 `continue_selected_flow` 已接入。
|
||||
- [x] 编写 schema 单元测试,验证候选流程只允许 `travel_application` 和 `travel_reimbursement`。[CONCEPT: 安全边界] 证据:`test_steward_intent_agent.py` 覆盖 function schema 枚举。
|
||||
|
||||
## 阶段三:LLM 意图识别主路径
|
||||
|
||||
- [x] 扩展 `steward_intent_agent.py` 的 function schema,要求模型输出 `pending_flow_confirmation` 和 `candidate_flows`。[CONCEPT: 后端] 证据:`test_steward_intent_agent.py` 通过。
|
||||
- [x] 更新系统提示词:不能把无明确动作的出差描述直接判定为申请;应结合语义、上下文和候选置信度决定是否确认。[CONCEPT: 背景与问题] 证据:`steward_intent_agent.py` system prompt 已要求低确定性返回 pending flow。
|
||||
- [x] 增加 fake LLM 测试:输入“2月20-23日去上海出差辅助国网仿生产环境部署”时,模型路径返回 `confirm_flow`。[CONCEPT: 指标与验收] 证据:`test_steward_planner_returns_pending_flow_confirmation_from_llm`。
|
||||
- [ ] 增加模型非法输出测试:非法字段、非法 flow、空候选项必须被服务端过滤或降级。[CONCEPT: 安全边界]
|
||||
|
||||
## 阶段四:状态合并与上下文记忆
|
||||
|
||||
- [x] 扩展 `steward_flow_state.py`,支持 `steward.flow_state.v1` 到 `steward.flow_state.v2` 的兼容升级。[CONCEPT: 风险与开放问题] 证据:`_normalize_state` 默认 v2 并保留 v1 核心结构。
|
||||
- [x] 支持将 `pending_flow_confirmation` 写入 state,并记录 source message、候选 flow 和确认原因。[CONCEPT: 业务 JSON 模板] 证据:`test_state_merge_plan_keeps_pending_flow_confirmation`。
|
||||
- [x] 支持用户选择候选 flow 后切换 `active_flow`,并把已识别字段合并到对应流程。[CONCEPT: 功能能力] 证据:`StewardFlowStateService.confirm_flow` 与 runtime 测试覆盖。
|
||||
- [x] 增加状态测试:多轮合并后 `flows.*.fields` 不出现非本体字段。[CONCEPT: 指标与验收] 证据:既有 `test_state_merge_filters_non_ontology_fields` 继续通过。
|
||||
- [ ] 增加状态测试:同一 `conversation_id` 下选择申请或报销不会丢失前一轮字段和证据。[CONCEPT: 数据与持久化]
|
||||
|
||||
## 阶段五:运行时决策
|
||||
|
||||
- [x] 扩展 `steward_runtime_decision_agent.py`,识别用户点击或输入“补办出差申请”“发起费用报销”。[CONCEPT: 后端] 证据:`_build_selected_flow_decision` 前置处理候选 flow。
|
||||
- [x] Runtime decision 输入为空时,从 `context_json.conversation_state.steward_state` 恢复状态。[CONCEPT: 输入] 证据:既有 `test_steward_runtime_decision_fallback_reads_persisted_steward_state` 继续通过。
|
||||
- [x] 用户选择申请后返回 `continue_selected_flow`,并设置 `active_flow=travel_application`。[CONCEPT: 指标与验收] 证据:`test_steward_runtime_decision_fallback_confirms_selected_flow`。
|
||||
- [x] 用户选择报销后返回 `continue_selected_flow`,并设置 `active_flow=travel_reimbursement`。[CONCEPT: 指标与验收] 证据:`test_steward_runtime_decision_fallback_confirms_reimbursement_flow`。
|
||||
- [x] 增加 runtime 测试,覆盖点击按钮和用户直接输入两种方式。[CONCEPT: 测试方案] 证据:runtime 单测覆盖申请/报销选择,接口 smoke 覆盖用户选择。
|
||||
|
||||
## 阶段六:前端候选流程展示
|
||||
|
||||
- [x] 在 `stewardPlanModel.js` 中把 `pending_flow_confirmation` 转成两个可点击建议动作。[CONCEPT: 前端] 证据:`steward-plan-model-pending-flow.test.mjs`。
|
||||
- [x] 在 `useStewardPlanFlow.js` 中渲染 Markdown 回复,确保标题、列表和重点加粗间距正常。[CONCEPT: 用户与场景] 证据:`buildStewardPlanMessageText` 对 `confirm_flow` 生成 Markdown 标题、列表和加粗内容。
|
||||
- [x] 在 `TravelReimbursementCreateView.js` 中处理候选流程点击:优先调用 runtime decision,不重新规划原始输入。[CONCEPT: 前端] 证据:`steward_confirm_flow` 分支调用 `handleStewardRuntimeDecision`。
|
||||
- [x] 在 `useTravelReimbursementSessionState.js` 中确认 `conversation_id` 和 `steward_state` 后续请求持续携带。[CONCEPT: 输入] 证据:现有 session state 与 `buildStewardPlanRequest` 已持续携带,无需新增改动。
|
||||
- [x] 增加或补充前端定向测试,覆盖候选按钮展示、点击后状态更新和不重复规划。[CONCEPT: 前端测试] 证据:新增 `steward-plan-model-pending-flow.test.mjs` 覆盖候选按钮,接口 smoke 覆盖选择后状态更新。
|
||||
|
||||
## 阶段七:接口与回归验证
|
||||
|
||||
- [x] 在容器中运行后端定向测试,单次命令超时控制在 60 秒内。[CONCEPT: 容器验证] 证据:`24 passed in 25.14s`。
|
||||
|
||||
```bash
|
||||
docker exec -w /app -e SERVER_VENV_DIR=/tmp/x-financial-server-venv x-financial-main /tmp/x-financial-server-venv/bin/pytest -q server/tests/test_steward_intent_agent.py server/tests/test_steward_model_plan_builder.py server/tests/test_steward_flow_state.py server/tests/test_steward_runtime_decision_agent.py
|
||||
```
|
||||
|
||||
- [x] 在容器中运行已有小财管家回归测试,确认旧的申请/报销拆分不退化。[CONCEPT: 测试方案] 证据:`test_steward_planner.py`、`test_steward_slot_decision_agent.py` 包含在后端定向测试中并通过。
|
||||
|
||||
```bash
|
||||
docker exec -w /app -e SERVER_VENV_DIR=/tmp/x-financial-server-venv x-financial-main /tmp/x-financial-server-venv/bin/pytest -q server/tests/test_steward_planner.py server/tests/test_steward_slot_decision_agent.py
|
||||
```
|
||||
|
||||
- [x] 在容器中运行前端构建。[CONCEPT: 容器验证] 证据:`docker exec -w /app/web x-financial-main npm run build` 成功。
|
||||
|
||||
```bash
|
||||
docker exec -w /app/web x-financial-main npm run build
|
||||
```
|
||||
|
||||
- [x] 手工验证小财管家输入“2月20-23日去上海出差辅助国网仿生产环境部署”,页面展示两个候选流程,未确认前不创建申请单或报销草稿。[CONCEPT: 指标与验收] 证据:接口 smoke 返回 `next_action=confirm_flow`、候选 `travel_application/travel_reimbursement`、`state_pending=pending`。
|
||||
|
||||
## 阶段八:文档同步
|
||||
|
||||
- [x] 实现过程中如调整 JSON 字段或接口契约,先更新 `CONCEPT.md`,再修改代码。[CONCEPT: 方案设计] 证据:已先新增 `CONCEPT.md` 与 `TODO.md`。
|
||||
- [x] 每完成一个阶段,在本 TODO 中勾选并补充证据,例如测试命令、文件名或接口返回要点。[CONCEPT: 测试方案] 证据:本文件已补充阶段证据。
|
||||
- [ ] 最终汇报工作区状态,不自动 commit/push,除非用户明确要求。[CONCEPT: 风险与开放问题]
|
||||
@@ -1,111 +0,0 @@
|
||||
# 工作台费用统计详情弹窗概念文档
|
||||
|
||||
## 功能一句话
|
||||
|
||||
在个人工作台的“费用统计”卡片中提供本地弹窗详情,让用户直接查看历史费用分布、单据处理时间和系统操作明细。
|
||||
|
||||
## 背景与问题
|
||||
|
||||
当前“费用统计”右上角的“查看详情”会进入助手问答,不符合用户期望的“像用户画像一样直接看详情”的操作方式。费用进度区域也存在两个可见性问题:右上角“全部进度”按钮没有实际承载完整列表能力,10 日以上分割标识靠左且不醒目。
|
||||
|
||||
本次调整需要让工作台成为个人费用操作的直接入口:用户不离开首页即可理解自己的费用结构、单据流转时间和近期系统动作。
|
||||
|
||||
## 目标与非目标
|
||||
|
||||
目标:
|
||||
|
||||
- 移除“费用进度”右上角的“全部进度”按钮,减少无效操作。
|
||||
- 将“10日以上”分割标识放在分割线中间,并使用更醒目的主题强调色。
|
||||
- 将“费用统计”的“查看详情”改为打开详情弹窗。
|
||||
- 弹窗展示历史报销费用分布、单据处理时间和系统操作详情。
|
||||
- 数据优先来自 `buildWorkbenchSummary` 已有的当前用户单据汇总,不新增后端接口。
|
||||
|
||||
非目标:
|
||||
|
||||
- 不新增后端 API。
|
||||
- 不改变报销单据审批状态计算规则。
|
||||
- 不替代用户画像详情弹窗。
|
||||
- 不做复杂图表库接入,避免为了一个工作台弹窗扩大依赖和维护面。
|
||||
|
||||
## 用户与场景
|
||||
|
||||
主要用户是个人员工和经常处理报销的业务人员。典型场景:
|
||||
|
||||
- 在首页查看本月报销情况后,想进一步确认自己的历史费用主要花在哪些类别。
|
||||
- 想知道近期单据从创建到当前状态大概处理了多久。
|
||||
- 想复盘系统里最近需要处理或已提醒的费用相关动作。
|
||||
|
||||
## 功能能力
|
||||
|
||||
### 费用分布
|
||||
|
||||
按单据标题、场景或备注归类费用类型,统计每类金额、单据数量和金额占比。详情区使用项目现有 `DonutChart` 饼图展示费用分布,并通过图例保留金额与占比信息。若数据不足,展示空状态。
|
||||
|
||||
### 处理时间
|
||||
|
||||
按单据创建、提交、更新或进度步骤时间推断处理耗时,输出可读的耗时文案,并展示当前状态和节点数量。
|
||||
|
||||
### 操作详情
|
||||
|
||||
基于待办、通知和进度项生成系统操作明细,帮助用户理解最近有哪些费用动作需要关注。
|
||||
|
||||
## 方案设计
|
||||
|
||||
前端实现:
|
||||
|
||||
- 在 `workbenchSummary.js` 中新增 `expenseStatsDetail` 汇总结构。
|
||||
- 新增 `ExpenseStatsDetailModal.vue`,复用 Element Plus `ElDialog`、`ElButton`、`ElTag` 的企业后台弹窗体验。
|
||||
- 费用分布展示复用现有 `DonutChart`,不手写临时 SVG、Canvas 或 CSS 饼图。
|
||||
- 在 `PersonalWorkbench.vue` 中接入弹窗状态,费用统计“查看详情”只打开弹窗。
|
||||
- 调整 `personal-workbench.css` 中长时间分割标识的居中与强调样式。
|
||||
|
||||
数据结构:
|
||||
|
||||
```js
|
||||
expenseStatsDetail: {
|
||||
distributionRows: [],
|
||||
processingRows: [],
|
||||
operationRows: []
|
||||
}
|
||||
```
|
||||
|
||||
## 算法与公式
|
||||
|
||||
费用类型金额占比:
|
||||
|
||||
$$
|
||||
percent_i = \frac{amount_i}{\sum_{k=1}^{n} amount_k} \times 100
|
||||
$$
|
||||
|
||||
单据处理耗时:
|
||||
|
||||
$$
|
||||
duration = latestTime - firstTime
|
||||
$$
|
||||
|
||||
其中 `firstTime` 优先取单据创建时间、提交时间或最早进度步骤时间,`latestTime` 优先取更新时间或最新进度步骤时间。
|
||||
|
||||
## 测试方案
|
||||
|
||||
- 源码测试确认费用进度不再渲染“全部进度”按钮。
|
||||
- 源码测试确认“费用统计”的“查看详情”打开弹窗而不是进入助手。
|
||||
- 单元测试确认 `buildWorkbenchSummary` 能生成费用分布、处理时间和操作明细。
|
||||
- 源码测试确认弹窗包含费用分布饼图、处理时间和系统操作详情区块。
|
||||
- 运行前端构建验证组件编译通过。
|
||||
|
||||
## 指标与验收
|
||||
|
||||
- “10日以上”标识位于分割线中间,且使用主题强调色。
|
||||
- “费用进度”卡片右上角不再出现“全部进度”。
|
||||
- 点击“费用统计”的“查看详情”打开详情弹窗。
|
||||
- 弹窗至少包含费用分布饼图、处理时间、系统操作详情三个信息区。
|
||||
- 相关测试与前端构建通过。
|
||||
|
||||
## 风险与开放问题
|
||||
|
||||
- 当前数据来自工作台前端汇总,历史维度受首页已加载单据范围影响;若后续需要跨年或分页全量统计,应补后端专用接口。
|
||||
- 单据类型归类依赖标题、场景和备注,属于前端轻量归类;后续可与 ontology 费用类别字段打通。
|
||||
|
||||
## 2026-06-03 饼图呈现修正
|
||||
|
||||
费用分布仍复用项目已有 `DonutChart`,但在费用统计详情弹窗内关闭组件自带图例,只保留一个环形饼图入口。费用类型、金额、笔数和占比改为右侧文字明细列表,避免环图主体和双列图例在同一卡片内被误认为出现两个饼图。
|
||||
@@ -1,31 +0,0 @@
|
||||
# 工作台费用统计详情弹窗 TODO
|
||||
|
||||
## 调研与契约
|
||||
|
||||
- [x] 核对 `PersonalWorkbench.vue`、工作台样式和现有用户画像弹窗结构。[CONCEPT: 方案设计] 证据:已确认工作台入口、`ExpenseProfileDetailModal.vue` 弹窗模式和 `personal-workbench.css` 分割样式。
|
||||
- [x] 明确费用详情弹窗的数据结构,并限制为前端工作台汇总数据。[CONCEPT: 功能能力] 证据:采用 `expenseStatsDetail`,由 `buildWorkbenchSummary` 基于当前用户单据生成。
|
||||
|
||||
## 前端实现
|
||||
|
||||
- [x] 移除费用进度卡片右上角“全部进度”按钮。[CONCEPT: 目标与非目标] 证据:`PersonalWorkbench.vue` 的费用进度标题区已移除该按钮。
|
||||
- [x] 调整“10日以上”分割标识为居中、醒目主题色样式。[CONCEPT: 指标与验收] 证据:`personal-workbench.css` 使用 `left: 50%`、`transform: translateX(-50%)` 和主题强调色。
|
||||
- [x] 在 `workbenchSummary.js` 生成费用分布、处理时间、系统操作详情数据。[CONCEPT: 算法与公式] 证据:新增 `expenseStatsDetail` 汇总结构。
|
||||
- [x] 新增费用统计详情弹窗组件,展示三个详情区块和空状态。[CONCEPT: 功能能力] 证据:新增 `ExpenseStatsDetailModal.vue`。
|
||||
- [x] 在 `PersonalWorkbench.vue` 接入弹窗状态与费用统计“查看详情”按钮。[CONCEPT: 方案设计] 证据:新增 `expenseStatsModalOpen` 与 `openExpenseStatsModal`。
|
||||
- [x] 将费用分布区从条形列表改为 `DonutChart` 饼图展示。[CONCEPT: 功能能力] 证据:`ExpenseStatsDetailModal.vue` 已接入 `DonutChart` 和 `distributionChartItems`。
|
||||
- [x] 关闭费用详情内 `DonutChart` 自带图例,改为单饼图加右侧文字明细。[CONCEPT: 2026-06-03 饼图呈现修正] 证据:`ExpenseStatsDetailModal.vue` 传入 `:show-legend="false"` 并新增 `expense-distribution-summary-list`。
|
||||
- [x] 为通用 `DonutChart` 增加可隐藏内置图例的开关,默认保持其它页面不变。[CONCEPT: 2026-06-03 饼图呈现修正] 证据:`DonutChart.vue` 新增 `showLegend` 默认值和 `donut-chart--legendless` 状态。
|
||||
|
||||
## 测试与验证
|
||||
|
||||
- [x] 补充工作台源码测试,覆盖按钮移除、弹窗接入和分割标识样式。[CONCEPT: 测试方案] 证据:`node web/tests/personal-workbench-assistant.test.mjs` 通过。
|
||||
- [x] 补充工作台汇总单元测试,覆盖详情数据生成。[CONCEPT: 测试方案] 证据:`node web/tests/workbench-summary.test.mjs` 通过。
|
||||
- [x] 补充弹窗源码测试,覆盖费用分布、处理时间、系统操作详情区块。[CONCEPT: 测试方案] 证据:`node web/tests/expense-stats-detail-modal.test.mjs` 通过。
|
||||
- [x] 运行前端定向测试和构建验证。[CONCEPT: 指标与验收] 证据:以上定向测试和 `npm.cmd --prefix web run build` 均通过。
|
||||
- [x] 更新弹窗源码测试,确认费用分布使用饼图组件。[CONCEPT: 测试方案] 证据:`node web/tests/expense-stats-detail-modal.test.mjs` 通过,`npm.cmd --prefix web run build` 通过。
|
||||
- [x] 更新弹窗与环图源码测试,确认详情弹窗只使用一个饼图入口且关闭内置图例。[CONCEPT: 2026-06-03 饼图呈现修正] 证据:`node web/tests/expense-stats-detail-modal.test.mjs` 与 `node web/tests/donut-chart.test.mjs` 通过。
|
||||
|
||||
## 交付
|
||||
|
||||
- [x] 复查本次暂存范围,避免纳入无关工作区改动。[CONCEPT: 风险与开放问题] 证据:`git diff --cached --name-only` 仅包含本次工作台弹窗、样式、汇总测试和开发文档。
|
||||
- [x] 提交并 push 本次功能分支。[CONCEPT: 指标与验收] 证据:本次单饼图修复完成后提交并推送当前分支。
|
||||
@@ -1,75 +0,0 @@
|
||||
# 工作台费用进度详情返回与类型列概念文档
|
||||
|
||||
## 功能一句话
|
||||
|
||||
让用户从首页费用进度进入单据详情后能返回首页,并在费用进度列表中直接看到每笔单据的费用类型。
|
||||
|
||||
## 背景与问题
|
||||
|
||||
当前首页费用进度点击单据后进入详情页,但详情页返回按钮默认回到单据中心,破坏了用户从首页查看进度的上下文。同时费用进度行只展示单号、标题、流程、状态和金额,用户需要点进详情才知道单据属于差旅、招待、办公等哪类费用。
|
||||
|
||||
## 目标与非目标
|
||||
|
||||
目标:
|
||||
|
||||
- 从首页费用进度进入详情时,详情页返回按钮回到个人工作台。
|
||||
- 从单据中心进入详情时,原有返回单据中心逻辑不变。
|
||||
- 在首页费用进度行新增“费用类型”列。
|
||||
- 费用类型优先使用单据已有类型字段,缺失时按标题、场景和备注轻量归类。
|
||||
|
||||
非目标:
|
||||
|
||||
- 不修改详情页主体内容。
|
||||
- 不新增后端接口。
|
||||
- 不改变单据中心列表、审批详情和其他来源的返回逻辑。
|
||||
|
||||
## 用户与场景
|
||||
|
||||
个人员工在首页查看最近费用进度,点击某笔单据进入详情核对处理情况。查看完后点击返回,应回到刚才的首页工作台继续看其他进度项,而不是跳到单据中心列表。
|
||||
|
||||
## 功能能力
|
||||
|
||||
- 首页打开详情时带入 `returnTo=workbench` 来源标记。
|
||||
- 详情页根据来源标记动态显示返回按钮文案并执行返回首页。
|
||||
- 费用进度数据新增 `expenseTypeLabel`。
|
||||
- 费用类型列在桌面端作为独立列展示,窄屏下按移动端布局折行展示。
|
||||
|
||||
## 方案设计
|
||||
|
||||
前端实现:
|
||||
|
||||
- `PersonalWorkbench.vue` 在 `open-document` 事件 payload 中补 `source: 'workbench'` 和 `returnTo: 'workbench'`。
|
||||
- `AppShellRouteView.vue` 接收工作台来源并传给 `openRequestDetail`。
|
||||
- `useAppShell.js` 在打开详情时写入查询参数,在关闭详情时根据查询参数返回工作台或单据中心。
|
||||
- `workbenchSummary.js` 在 `progressItems` 中补费用类型字段。
|
||||
- `personal-workbench.css` 与响应式样式新增费用类型列。
|
||||
|
||||
## 算法与公式
|
||||
|
||||
当前功能不涉及复杂数学公式。
|
||||
|
||||
费用类型归类优先级:
|
||||
|
||||
1. 单据显式字段:`expenseCategory`、`expense_type`、`category` 等。
|
||||
2. 文本规则:从场景、标题、备注和描述中匹配差旅、招待、办公、培训、市场等关键词。
|
||||
3. 兜底为“其他费用”。
|
||||
|
||||
## 测试方案
|
||||
|
||||
- 源码测试确认首页费用进度打开详情时带 `returnTo=workbench`。
|
||||
- 源码测试确认详情返回文案和关闭逻辑支持工作台来源。
|
||||
- 单元测试确认 `progressItems` 输出费用类型字段。
|
||||
- 源码测试确认费用进度模板和样式包含费用类型列。
|
||||
- 运行前端定向测试与构建。
|
||||
|
||||
## 指标与验收
|
||||
|
||||
- 从首页费用进度进入详情后,返回按钮回到个人工作台。
|
||||
- 从单据中心进入详情后,返回按钮仍回到单据中心。
|
||||
- 首页费用进度行可直接看到费用类型。
|
||||
- 相关定向测试与前端构建通过。
|
||||
|
||||
## 风险与开放问题
|
||||
|
||||
- 当前费用类型归类仍是前端轻量归类。后续若后端已有稳定 ontology 类型字段,应优先接入 canonical 字段。
|
||||
- 路由查询参数只用于详情返回来源,不应影响单据筛选和详情数据加载。
|
||||
@@ -1,25 +0,0 @@
|
||||
# 工作台费用进度详情返回与类型列 TODO
|
||||
|
||||
## 调研与契约
|
||||
|
||||
- [x] 核对首页费用进度点击链路、详情页返回逻辑和当前进度行样式。[CONCEPT: 方案设计] 证据:已确认 `PersonalWorkbench.vue` 发出 `open-document`,`AppShellRouteView.vue` 转入详情,`useAppShell.js` 默认返回单据中心。
|
||||
- [x] 明确来源标记与费用类型字段的前端契约。[CONCEPT: 功能能力] 证据:采用 `returnTo: 'workbench'` 与 `expenseTypeLabel`。
|
||||
|
||||
## 前端实现
|
||||
|
||||
- [x] 首页费用进度打开详情时带入工作台返回来源。[CONCEPT: 方案设计] 证据:`PersonalWorkbench.vue` 的 `open-document` payload 已包含 `source` 与 `returnTo`。
|
||||
- [x] 详情页关闭逻辑按来源返回工作台或单据中心。[CONCEPT: 功能能力] 证据:`useAppShell.js` 根据 `route.query.returnTo` 选择 `app-workbench` 或 `app-documents`。
|
||||
- [x] 工作台进度汇总新增费用类型字段。[CONCEPT: 功能能力] 证据:`workbenchSummary.js` 的 `progressItems` 输出 `expenseTypeLabel`。
|
||||
- [x] 首页费用进度行新增费用类型列及响应式样式。[CONCEPT: 指标与验收] 证据:`PersonalWorkbench.vue` 新增 `progress-type`,样式和响应式布局已更新。
|
||||
|
||||
## 测试与验证
|
||||
|
||||
- [x] 补充详情返回来源源码测试。[CONCEPT: 测试方案] 证据:`node web/tests/workbench-detail-return.test.mjs` 通过。
|
||||
- [x] 补充费用进度类型列源码测试。[CONCEPT: 测试方案] 证据:`node web/tests/personal-workbench-assistant.test.mjs` 通过。
|
||||
- [x] 补充工作台汇总单元测试,覆盖费用类型字段。[CONCEPT: 测试方案] 证据:`node web/tests/workbench-summary.test.mjs` 通过。
|
||||
- [x] 运行定向测试和前端构建。[CONCEPT: 指标与验收] 证据:以上定向测试和 `npm.cmd --prefix web run build` 均通过。
|
||||
|
||||
## 交付
|
||||
|
||||
- [x] 复查暂存范围,避免纳入无关工作区改动。[CONCEPT: 风险与开放问题] 证据:`git diff --cached --name-only` 仅包含本次工作台进度、返回来源、测试和开发文档。
|
||||
- [ ] 提交并 push 本次功能分支。[CONCEPT: 指标与验收]
|
||||
@@ -1,151 +0,0 @@
|
||||
# 数字员工工作看板概念文档
|
||||
|
||||
## 功能一句话
|
||||
|
||||
在分析看板中新增“数字员工看板”,让用户用一个统一视角看到数字员工每天执行了哪些后台分析、整理、积累和评估工作,以及这些工作产生了什么业务结果。
|
||||
|
||||
## 背景与问题
|
||||
|
||||
当前数字员工已经有“员工技能”和“工作记录”页面,但工作记录偏运行明细,适合追溯单次任务。管理者在分析看板中缺少一个汇总视角,无法快速回答:
|
||||
|
||||
- 今天数字员工是否真的在工作。
|
||||
- 哪些技能执行最多。
|
||||
- 成功、失败、运行中的任务分别是多少。
|
||||
- 风险图谱、风险线索、员工画像和知识整理分别产出了什么。
|
||||
- 最近失败或异常的后台任务是否需要处理。
|
||||
|
||||
新增看板后,分析看板承担“经营和运行洞察”入口,数字员工页面继续承担“技能配置、工作记录详情和人工操作”入口。
|
||||
|
||||
## 目标与非目标
|
||||
|
||||
### 目标
|
||||
|
||||
- 在分析看板顶部切换项中新增“数字员工看板”。
|
||||
- 用真实 `AgentRun` 和 `AgentToolCall` 数据聚合数字员工工作,不使用演示数据伪装真实结果。
|
||||
- 展示最近 N 天的工作总数、成功数、失败数、运行中数量、产出量和日趋势。
|
||||
- 区分技能类型:积累、升级、整理、评估。
|
||||
- 展示最近工作记录,用户能直观看到每天做了什么和产出了什么。
|
||||
|
||||
### 非目标
|
||||
|
||||
- 不替代数字员工页面的“员工技能”和“工作记录”详情。
|
||||
- 不让数字员工执行规则中心主流程,也不让数字员工定义、发布或确认风险规则。
|
||||
- 不展示内部实现名称或技术代号,页面文案统一使用“数字员工”。
|
||||
- 不在本期新增新的算法执行器,只消费已有执行结果做分析看板聚合。
|
||||
|
||||
## 用户与场景
|
||||
|
||||
- 财务负责人:查看数字员工每天是否持续产出知识整理、风险观察、画像快照和线索。
|
||||
- 风控与审计人员:查看评估、升级类任务的失败与产出情况,判断是否需要复核。
|
||||
- 系统管理员:观察后台任务是否运行稳定,识别失败任务和数据异常。
|
||||
|
||||
## 功能能力
|
||||
|
||||
### 输入
|
||||
|
||||
- `agent_runs`:数字员工运行记录。
|
||||
- `agent_tool_calls`:每次运行中的工具调用与响应摘要。
|
||||
- `route_json` / `request_json` / `response_json`:用于识别任务类型、任务编码、报告类型和产出指标。
|
||||
|
||||
### 输出
|
||||
|
||||
- KPI 指标:工作总数、成功数量、失败数量、运行中数量、业务产出、成功率。
|
||||
- 每日工作趋势:按日期聚合总数、成功、失败和主要产出量。
|
||||
- 技能类型分布:积累、升级、整理、评估。
|
||||
- 工作模块排行:财务风险图谱巡检、员工行为画像巡检、风险线索归集、知识制度整理等。
|
||||
- 最近工作记录:任务名称、状态、开始时间、耗时、摘要和关键指标。
|
||||
|
||||
### 状态
|
||||
|
||||
- 成功:`succeeded`、`success`、`completed`、`done`。
|
||||
- 失败:`failed`、`failure`、`error`、`errored`。
|
||||
- 运行中:`running`、`pending`。
|
||||
- 其他状态统一归入“其他”,但不丢弃记录。
|
||||
|
||||
### 权限与边界
|
||||
|
||||
- 本期沿用分析看板已有访问控制,不新增独立权限。
|
||||
- 看板只读,不提供运行、定时、编辑技能等操作。
|
||||
- 单次运行详情仍在数字员工工作记录页面处理。
|
||||
|
||||
## 方案设计
|
||||
|
||||
### 后端
|
||||
|
||||
新增 `DigitalEmployeeDashboardService`:
|
||||
|
||||
- 从 `AgentRun` 查询最近 `days` 天数据,最多取 `limit` 条。
|
||||
- 通过 `agent == "hermes"`、`source == "schedule"`、`route_json` 任务字段、工具名 `digital_employee.*` 和知识整理任务类型识别数字员工工作。
|
||||
- 从工具响应中提取业务产出指标,例如风险观察数、风险线索数、画像快照数、知识文档数。
|
||||
- 返回稳定结构,前端只负责展示,不重复推断核心聚合逻辑。
|
||||
|
||||
新增接口:
|
||||
|
||||
```http
|
||||
GET /api/v1/analytics/digital-employee-dashboard?days=7&limit=300
|
||||
```
|
||||
|
||||
### 前端
|
||||
|
||||
新增 `DigitalEmployeeDashboard.vue`:
|
||||
|
||||
- 复用现有 `OverviewView` 的 KPI 卡片、`dashboard-card`、`BarChart` 和企业级直角视觉。
|
||||
- 使用两列到多列的看板网格,避免新增营销化卡片风格。
|
||||
- 状态、空数据和加载错误保持与风险看板一致。
|
||||
|
||||
接入点:
|
||||
|
||||
- `TopBar.vue` 增加“数字员工看板”切换项。
|
||||
- `OverviewView.vue` 新增 `activeDashboard === "digitalEmployee"` 分支。
|
||||
- `useOverviewView.js` 新增数据加载、KPI 映射、趋势行和排行行。
|
||||
- `analytics.js` 新增接口调用和字段归一化。
|
||||
|
||||
## 算法与公式
|
||||
|
||||
### 成功率
|
||||
|
||||
$$
|
||||
success\_rate = \frac{success\_runs}{max(total\_runs, 1)}
|
||||
$$
|
||||
|
||||
### 失败率
|
||||
|
||||
$$
|
||||
failure\_rate = \frac{failed\_runs}{max(total\_runs, 1)}
|
||||
$$
|
||||
|
||||
### 业务产出量
|
||||
|
||||
$$
|
||||
business\_outputs = risk\_observations + risk\_clues + profile\_snapshots + knowledge\_documents
|
||||
$$
|
||||
|
||||
### 日工作负载
|
||||
|
||||
$$
|
||||
daily\_workload_d = total\_runs_d + business\_outputs_d
|
||||
$$
|
||||
|
||||
以上公式只用于看板展示和排序,不参与规则中心决策。
|
||||
|
||||
## 测试方案
|
||||
|
||||
- 后端单元测试:构造数字员工运行、普通智能体运行、失败运行和工具响应,验证聚合结果。
|
||||
- 接口测试:验证 `/analytics/digital-employee-dashboard` 返回字段结构和空数据行为。
|
||||
- 前端静态测试:验证切换项、接口地址、组件分支和核心文案存在。
|
||||
- 构建验证:运行前端构建,确保新增 Vue 组件可编译。
|
||||
- 容器验证:在 `x-financial-main` 中运行后端定向测试,并调用真实接口确认返回 JSON。
|
||||
|
||||
## 指标与验收
|
||||
|
||||
- 分析看板切换中出现“数字员工看板”。
|
||||
- 选择该看板后页面显示 KPI、每日工作、技能类型分布、任务排行和最近工作。
|
||||
- 没有真实数据时显示空状态,不使用伪造业务数。
|
||||
- 接口返回 `has_real_data`,前端可据此判断真实数据状态。
|
||||
- 后端定向测试和前端定向测试通过。
|
||||
|
||||
## 风险与开放问题
|
||||
|
||||
- 旧版 `hermes_task_execution_logs` 中的日志没有完整工具响应,本期优先以 `AgentRun` 为准;如需兼容旧日志,可后续做补充。
|
||||
- 部分新增技能当前可能只有定义,未必已有真实执行结果,看板会显示为 0 或不出现。
|
||||
- 如果后续新增数字员工技能,需要同步更新任务类型映射,避免看板归类为“其他”。
|
||||
@@ -1,29 +0,0 @@
|
||||
# 数字员工工作看板 TODO
|
||||
|
||||
## 调研与边界
|
||||
|
||||
- [x] 梳理分析看板切换入口和现有数据流。[CONCEPT: 方案设计] 证据:`TopBar.vue`、`OverviewView.vue`、`useOverviewView.js`。
|
||||
- [x] 梳理数字员工工作记录数据来源。[CONCEPT: 功能能力] 证据:`AgentRun`、`AgentToolCall`、`digitalEmployeeWorkRecordsModel.js`。
|
||||
- [x] 明确本期非目标:不替代数字员工详情、不执行规则中心主流程、不使用演示数据。[CONCEPT: 目标与非目标]
|
||||
|
||||
## 契约与后端
|
||||
|
||||
- [x] 新增数字员工看板响应 schema。[CONCEPT: 后端] 证据:`DigitalEmployeeDashboardRead`。
|
||||
- [x] 新增 `DigitalEmployeeDashboardService` 聚合运行记录、任务分布、日趋势和最近工作。[CONCEPT: 后端] 证据:`digital_employee_dashboard.py`。
|
||||
- [x] 新增 `/analytics/digital-employee-dashboard` 接口。[CONCEPT: 后端] 证据:`analytics.py` 路由和容器接口返回。
|
||||
- [x] 补后端定向测试覆盖成功、失败、非数字员工过滤和业务产出统计。[CONCEPT: 测试方案] 证据:`server/tests/test_digital_employee_dashboard_service.py`,2 passed。
|
||||
|
||||
## 前端
|
||||
|
||||
- [x] 在分析看板切换项中增加“数字员工看板”。[CONCEPT: 前端] 证据:`TopBar.vue`。
|
||||
- [x] 在 `analytics.js` 新增接口调用和字段归一化。[CONCEPT: 前端] 证据:`fetchDigitalEmployeeDashboard` 和 `normalizeDigitalEmployeeDashboardPayload`。
|
||||
- [x] 在 `useOverviewView.js` 接入加载状态、KPI、趋势和排行数据。[CONCEPT: 前端] 证据:`useOverviewView.js` 与 `overviewDigitalEmployeeDashboardModel.js`。
|
||||
- [x] 新增 `DigitalEmployeeDashboard.vue`,复用现有企业看板风格。[CONCEPT: 前端] 证据:看板组件和 ECharts 日趋势组件。
|
||||
- [x] 在 `OverviewView.vue` 增加数字员工看板分支。[CONCEPT: 前端] 证据:`activeDashboard === "digitalEmployee"`。
|
||||
|
||||
## 验证与验收
|
||||
|
||||
- [x] 运行后端定向测试,超时不超过 60s。[CONCEPT: 测试方案] 证据:`timeout 60s ... pytest server/tests/test_digital_employee_dashboard_service.py -q`,2 passed。
|
||||
- [x] 运行前端定向测试或构建验证。[CONCEPT: 测试方案] 证据:`node --test web/tests/digital-employee-dashboard.test.mjs`,3 passed;`npm.cmd --prefix web run build` 通过。
|
||||
- [x] 在 Docker 容器中调用真实接口验证 JSON 返回。[CONCEPT: 指标与验收] 证据:`GET /api/v1/analytics/digital-employee-dashboard?days=7&limit=300` 返回 `True 1 1 知识制度整理`。
|
||||
- [x] 更新本 TODO 的完成证据。[CONCEPT: 指标与验收] 证据:本文档已更新。
|
||||
@@ -1,133 +0,0 @@
|
||||
# 数字员工能力库扩展概念文档
|
||||
|
||||
更新日期:2026-05-31
|
||||
|
||||
## 功能一句话
|
||||
|
||||
把数字员工从少量后台任务扩展为覆盖事实抽取、规则命中分析、资产积累、报告生成和人工复核辅助的企业级后台分析能力库。
|
||||
|
||||
## 背景与问题
|
||||
|
||||
当前员工技能数量偏少,只有制度整理、风险图谱巡检、员工画像巡检和少量复核辅助能力。页面观感更像技术演示,不像完整的财务数字员工能力矩阵。
|
||||
|
||||
需要把已有风险图谱、制度知识、画像基线、反馈池、回放评测等算法资产拆成用户能理解的员工技能,让列表规模、分类结构和详情内容都更完整。
|
||||
|
||||
同时必须收敛数字员工边界:数字员工不是风险专家,也不是规则制定者。风险口径、规则内容、制度解释和最终判断由人负责;规则中心执行归属外层智能体流程,数字员工只负责读取事实、规则命中和反馈结果,生成后台分析、报告、知识库材料和待人工复核线索。
|
||||
|
||||
## 目标
|
||||
|
||||
- 员工技能数量扩展到不少于 16 个。
|
||||
- 保持四类技能:积累、升级、整理、评估。
|
||||
- 每个技能都有名称、描述、技能包、分类、执行场景、输入、输出、是否定时、是否写入工作记录。
|
||||
- 新增技能进入资产种子和运行时补齐逻辑,已有数据库启动后也能自动补齐。
|
||||
- 新增技能包落在 `server/src/app/skills/domain`,便于后续同步到数字员工运行侧。
|
||||
- 明确技能边界:输出事实、规则命中和待人工确认线索,不输出正式规则结论或规则变更裁判。
|
||||
|
||||
## 非目标
|
||||
|
||||
- 本轮不引入新的数据库结构变更。
|
||||
- 本轮不要求所有新增技能都接入真实执行器。
|
||||
- 本轮不复制竞品术语或页面包装,只做 X-Financial 自有能力命名。
|
||||
- 本轮不让数字员工总结风险规则、发明新规则、修改规则中心或替代人工确认风险。
|
||||
|
||||
## 用户与场景
|
||||
|
||||
- 风控管理员:查看评估类和升级类技能,理解规则命中分析、异常线索、人工复核样本和回放评测能力。
|
||||
- 财务制度管理员:查看整理类技能,维护制度条款、政策口径和规则命中样本。
|
||||
- 数据治理人员:查看积累类技能,理解员工、部门、供应商和反馈样本如何沉淀。
|
||||
- 系统管理员:配置定时计划、查看工作记录和执行结果。
|
||||
|
||||
## 功能能力
|
||||
|
||||
完整员工技能库按四类组织:
|
||||
|
||||
- 整理:财务制度、制度条款、政策口径、规则命中样本。
|
||||
- 积累:员工画像、部门基线、供应商画像、误报样本、反馈样本。
|
||||
- 评估:风险图谱、多凭证一致性、时空一致性、预算超标、供应商异常关系。
|
||||
- 升级:风险线索归集、算法回放、制度引用缺口提示和人工复核材料整理。
|
||||
|
||||
每个技能需要提供:
|
||||
|
||||
- `skill_name`:技能包目录名。
|
||||
- `skill_category`:积累、升级、整理、评估之一。
|
||||
- `task_type`:由任务 code 派生。
|
||||
- `schedule` / `cron_expression`:默认定时计划。
|
||||
- `input_sources`:输入来源。
|
||||
- `output_format`:产出格式。
|
||||
- `writes_work_record`:是否产出工作记录。
|
||||
- `execution_strategy`:真实执行、复用现有扫描器或定义先行。
|
||||
- `role_boundary`:规则由人定义、风险由人确认、主流程由外层智能体执行,数字员工只做后台分析、报告生成和知识沉淀。
|
||||
- `allowed_outputs`:只允许输出 `facts`、`rule_hits`、`risk_clues`、`evidence_refs`、`human_review_required` 等受控字段。
|
||||
|
||||
## 数字员工边界
|
||||
|
||||
数字员工允许做三件事:
|
||||
|
||||
- 事实抽取:从申请单、报销单、票据、附件、审批记录中抽取金额、时间、地点、人员、供应商、票据号、申请关系等事实。
|
||||
- 规则命中分析:读取外层智能体流程已经产生的规则命中结果、字段依据和原始证据,用于后台报告与复核材料整理。
|
||||
- 线索归集:基于事实和规则命中输出“待人工复核”的潜在线索,不能把线索升级为正式风险结论。
|
||||
|
||||
数字员工禁止做四件事:
|
||||
|
||||
- 不总结或发明风险规则。
|
||||
- 不修改、发布、删除规则中心规则。
|
||||
- 不把潜在线索判定为最终违规结论。
|
||||
- 不替代财务、风控或管理员进行制度解释和风险确认。
|
||||
|
||||
## 方案设计
|
||||
|
||||
### 后端
|
||||
|
||||
- 在 `agent_foundation_constants.py` 增加新增任务 code 和分类映射。
|
||||
- 在 `agent_foundation_digital_employee_tasks.py` 增加运行时任务规格。
|
||||
- 在初始种子流程完成基础任务 flush 后,调用运行时补齐逻辑,保证新库完整落库。
|
||||
- 新增技能包目录和 `SKILL.md`,内容包含功能说明、执行时机、输入输出和边界。
|
||||
- 将容易越权的“规则发现、规则模板整理、制度缺口优化”收敛为“风险线索归集、规则命中样本整理、制度引用缺口提示”。
|
||||
|
||||
### 前端
|
||||
|
||||
前端列表已按资产接口读取任务类资产,不需要新增页面结构。新增任务落库后会自动进入员工技能列表,并使用已有筛选、分类和详情展示。
|
||||
|
||||
### 算法与公式
|
||||
|
||||
本轮主要扩展能力目录和角色边界,不新增评分公式。后续每个技能接入真实算法时,再在对应算法文档中补充公式。
|
||||
|
||||
数字员工输出的线索置信度只能作为排序依据,不能作为最终风险裁判:
|
||||
|
||||
$$
|
||||
risk\_clue = f(facts, rule\_hits, evidence\_quality)
|
||||
$$
|
||||
|
||||
其中 `facts` 来自申请与报销事实,`rule_hits` 来自外层智能体流程或规则中心已经产生的命中结果,`evidence_quality` 表示证据完整度。数字员工不触发规则主流程,最终是否构成风险由人工复核或规则中心既有处置流程决定。
|
||||
|
||||
### 后台分析闭环
|
||||
|
||||
风险线索归集不是规则生产流程,而是后台分析闭环的一环:
|
||||
|
||||
- 工作记录详情展示本次归集的事实、规则命中、待复核线索和近期反馈样本。
|
||||
- 风险看板展示待复核线索数和反馈样本数,用于观察后台分析是否形成可复盘资产。
|
||||
- 人工反馈仍写入风险观察反馈池,数字员工只读取反馈池做线索排序、复核材料整理和后续报告生成。
|
||||
|
||||
## 测试方案
|
||||
|
||||
- 单元测试:校验数字员工运行时任务规格数量、分类覆盖、技能包目录存在、任务 code 唯一。
|
||||
- 配置测试:校验每个任务配置都包含 `skill_name`、`output_format`、`skill_category_options`。
|
||||
- 容器验证:在 `x-financial-main:/app/server` 运行定向测试。
|
||||
- 手工验收:进入数字员工员工技能列表,确认技能数量和分类明显完整。
|
||||
- 接口验收:风险看板接口返回 `risk_clue_count` 和 `feedback_sample_count`,工作记录详情能展示风险线索归集的反馈样本摘要。
|
||||
|
||||
## 指标与验收
|
||||
|
||||
- 员工技能总数不少于 17 个。
|
||||
- 四类分类都有技能。
|
||||
- 新增技能包全部存在 `SKILL.md`。
|
||||
- 定向测试通过。
|
||||
- 风险看板不再展示候选规则指标,改为待复核线索和反馈样本。
|
||||
- 不引入数据库迁移和破坏性变更。
|
||||
|
||||
## 风险与开放问题
|
||||
|
||||
- 新增技能中部分为“定义先行”,立即运行时需要后续逐步接入真实执行器。
|
||||
- 如果用户希望每个技能都能立即产出真实结果,需要继续拆分执行服务和工作记录产物。
|
||||
- 已接入风险线索归集真实执行器,后续应继续把多凭证、时空、预算、供应商异常从风险图谱主引擎中拆成独立算法模块。
|
||||
- 若技能命名或说明再次出现“数字员工承担规则主流程、规则发现、规则优化、自动总结风险”等表述,应优先改为读取规则命中结果、事实、线索、复核材料等受控表述。
|
||||
@@ -1,56 +0,0 @@
|
||||
# 数字员工能力库扩展 TODO
|
||||
|
||||
更新日期:2026-05-31
|
||||
|
||||
## 1. 调研与契约
|
||||
|
||||
- [x] 复核当前员工技能数量、分类和技能包目录。[CONCEPT: 背景与问题] 证据:当前已有基础技能包:制度整理、风险图谱巡检、员工画像巡检、风险线索归集。
|
||||
- [x] 定义完整能力矩阵,覆盖积累、升级、整理、评估四类。[CONCEPT: 功能能力] 证据:`CONCEPT.md` 已列出 17 个目标技能。
|
||||
|
||||
## 2. 后端资产
|
||||
|
||||
- [x] 增加新增数字员工任务 code 和分类映射。[CONCEPT: 后端] 证据:`agent_foundation_constants.py` 已新增 13 个任务 code,`DIGITAL_EMPLOYEE_TASK_CATEGORY_MAP` 覆盖四类分类。
|
||||
- [x] 增加运行时任务规格,保证已有数据库可自动补齐新增员工技能。[CONCEPT: 后端] 证据:`agent_foundation_digital_employee_tasks.py` 已扩展到 16 个运行时任务规格,新增技能均包含 `skill_name/input_sources/output_format/execution_strategy`。
|
||||
- [x] 调整初始种子流程,保证空库初始化时也能落齐完整员工技能库。[CONCEPT: 后端] 证据:`agent_foundation_asset_seed.py` 在基础资产 `flush` 后调用 `_upsert_runtime_digital_employee_tasks()`,空库初始化会补齐完整运行时技能。
|
||||
|
||||
## 3. 技能包
|
||||
|
||||
- [x] 新增制度条款、政策口径、规则命中样本等整理类技能包。[CONCEPT: 功能能力] 证据:已新增 `finance-policy-clause-extractor`、`expense-policy-alignment`、`rule-execution-case-organizer` 技能包。
|
||||
- [x] 新增部门基线、供应商画像、误报样本、反馈样本等积累类技能包。[CONCEPT: 功能能力] 证据:已新增 `department-expense-baseline-accumulator`、`supplier-risk-profile-accumulator`、`false-positive-sample-accumulator`、`risk-feedback-sample-accumulator` 技能包。
|
||||
- [x] 新增多凭证、时空、预算、供应商关系等评估类技能包。[CONCEPT: 功能能力] 证据:已新增 `multi-evidence-consistency-evaluator`、`travel-spatiotemporal-consistency-evaluator`、`budget-overrun-precontrol-evaluator`、`supplier-abnormal-relation-evaluator` 技能包。
|
||||
- [x] 新增回放评测、制度引用缺口提示等升级类技能包。[CONCEPT: 功能能力] 证据:已新增 `risk-algorithm-replay-evaluator`、`policy-reference-gap-hinter` 技能包。
|
||||
|
||||
## 4. 测试与验收
|
||||
|
||||
- [x] 增加数字员工技能目录测试,校验任务 code 唯一、分类覆盖、技能包存在。[CONCEPT: 测试方案] 证据:新增 `tests/test_digital_employee_skill_catalog.py` 覆盖任务数量、分类、配置和技能包。
|
||||
- [x] 在 Docker 容器 `x-financial-main:/app` 运行定向测试,60s 内完成。[CONCEPT: 测试方案] 证据:`docker exec x-financial-main bash -lc "cd /app && timeout 60s /tmp/x-financial-server-venv/bin/python -m pytest server/tests/test_digital_employee_skill_catalog.py -q"` 通过,3 个测试通过。
|
||||
- [x] 确认最终员工技能总数不少于 17 个,四类分类都有技能。[CONCEPT: 指标与验收] 证据:测试断言运行时 16 个技能加 `整理公司财务知识制度` 共 17 个,分类覆盖积累、升级、整理、评估。
|
||||
|
||||
## 5. 边界收敛
|
||||
|
||||
- [x] 调整概念文档,明确数字员工不总结风险规则、不发明规则、不替代人工确认风险。[CONCEPT: 数字员工边界] 证据:`CONCEPT.md` 和 `hermes-risk-graph-algorithm/CONCEPT.md` 已把数字员工边界收敛为事实抽取、规则命中结果读取、后台分析和待复核线索归集。
|
||||
- [x] 将“风险规则候选发现、风险规则模板整理、制度缺口与规则变更建议”收敛为事实、规则命中和人工复核辅助类技能。[CONCEPT: 功能能力] 证据:运行时技能已改为 `risk-clue-collector`、`rule-execution-case-organizer`、`policy-reference-gap-hinter`。
|
||||
- [x] 在技能配置中增加 `role_boundary` 和 `allowed_outputs`,约束输出只能是事实、规则命中、线索和证据引用。[CONCEPT: 数字员工边界] 证据:`agent_foundation_digital_employee_tasks.py` 为运行时技能配置写入 `role_boundary`、`allowed_outputs` 和 `writes_rules=false`。
|
||||
- [x] 更新技能包 Markdown,禁止数字员工发布、改写、总结规则,风险线索必须待人工复核。[CONCEPT: 后端] 证据:`risk-clue-collector`、`rule-execution-case-organizer`、`policy-reference-gap-hinter` 及兼容别名技能包均已声明禁止生成、改写或发布规则。
|
||||
- [x] 增加目录测试,防止数字员工技能重新出现自动发布、规则变更、候选规则生成等越权语义。[CONCEPT: 测试方案] 证据:`test_digital_employee_skills_do_not_cross_rule_governance_boundary` 已断言旧技能名和危险输出格式不再进入数字员工目录。
|
||||
|
||||
## 7. 流程边界收敛
|
||||
|
||||
- [x] 明确规则中心命中结果归属外层智能体流程,数字员工只消费规则命中结果。[CONCEPT: 数字员工边界] 证据:`CONCEPT.md` 已改为“规则命中分析”,并声明数字员工不触发规则主流程。
|
||||
- [x] 更新技能与配置文案,禁止数字员工被描述为规则主流程处理器。[CONCEPT: 后端] 证据:`agent_foundation_digital_employee_tasks.py`、`risk-clue-collector`、`rule-execution-case-organizer` 及兼容别名技能包均已改为后台分析和复核材料口径。
|
||||
- [x] 增加测试,防止 `role_boundary` 再次出现规则主流程越界表述。[CONCEPT: 测试方案] 证据:`test_digital_employee_runtime_specs_build_display_ready_config` 已覆盖主流程归属和禁止数字员工承担规则主流程职责。
|
||||
|
||||
## 6. 风险线索归集真实执行器
|
||||
|
||||
- [x] 新增 `HermesRiskClueCollectorService`,读取申请/报销事实、规则命中、风险观察和人工反馈,输出 `risk_clue_review_packet`。[CONCEPT: 算法与公式] 证据:`hermes_risk_clue_collector.py` 输出 `facts/rule_hits/risk_clues/evidence_refs/human_review_required`。
|
||||
- [x] 将 `risk_clue_collect` 接入数字员工立即运行分发。[CONCEPT: 后端] 证据:`orchestrator_execution.py` 已新增 `digital_employee.risk_clue.collect` 工具调用,`test_schedule_digital_employee_task_runs_real_service` 覆盖分发。
|
||||
- [x] 将 `risk_clue_collect` 接入 Hermes 定时调度。[CONCEPT: 后端] 证据:`hermes_scheduler.py` 已新增 `risk_clue_collect` 分支并写入执行摘要。
|
||||
- [x] 工作记录详情识别风险线索归集产物,展示事实、规则命中、待复核线索和证据引用计数。[CONCEPT: 前端] 证据:`digitalEmployeeWorkRecordsModel.js` 和 `DigitalEmployeeRunProducts.vue` 已支持 `risk_clue` 产物,前端测试覆盖。
|
||||
- [x] 增加执行器测试,验证不写规则、不输出候选规则、线索必须待人工复核。[CONCEPT: 测试方案] 证据:`test_hermes_risk_clue_collector.py` 通过,断言 `writes_rules=false`、`human_review_required=true` 和无 `candidate_risk_rules/auto_publish`。
|
||||
|
||||
## 8. 后台分析闭环
|
||||
|
||||
- [x] 风险线索归集产物补充观察键、反馈状态和近期反馈样本摘要,方便工作记录详情定位复核上下文。[CONCEPT: 后台分析闭环] 证据:`hermes_risk_clue_collector.py` 输出 `observation_key/feedback_status/next_action/feedback_summary`,`DigitalEmployeeRunProducts.vue` 展示反馈样本。
|
||||
- [x] 风险看板聚合接口补充 `risk_clue_count` 与 `feedback_sample_count`,把数字员工后台分析结果接入看板指标。[CONCEPT: 后台分析闭环] 证据:`RiskObservationDashboardRead` 与 `RiskObservationService.summarize_dashboard()` 已输出线索数和反馈样本数。
|
||||
- [x] 风险看板前端移除“候选规则”指标,改为“待复核线索”和“反馈样本”。[CONCEPT: 指标与验收] 证据:`RiskObservationDashboard.vue` 的算法闭环效果区已展示 `待复核线索/反馈样本`,前端测试断言不再出现候选规则。
|
||||
- [x] 增加后端与前端定向测试,并在 Docker 容器内验证核心后端测试通过。[CONCEPT: 测试方案] 证据:`pytest` 定向测试 8 个通过,`node --test` 前端定向测试 8 个通过。
|
||||
@@ -1,328 +0,0 @@
|
||||
# 数字员工财务报告体系概念文档
|
||||
|
||||
更新日期:2026-06-02
|
||||
|
||||
## 功能一句话
|
||||
|
||||
让数字员工每周、每季、每年自动汇总企业费用、预算、流程、画像和风险经验,生成图文并茂的 PDF 报告,并按计划投递给财务管理人员。
|
||||
|
||||
## 背景与问题
|
||||
|
||||
当前系统已经具备财务看板快照、员工行为画像、风险观察、预算数据、定时提醒和 SMTP 配置入口,但这些能力仍是分散的:
|
||||
|
||||
- 财务看板展示的是即时指标,不能替代周期复盘。
|
||||
- 数字员工已有运行记录,但缺少能给管理层阅读的正式 PDF 报告。
|
||||
- 员工画像、预算偏差、风险线索和提醒效果没有被串成企业经验。
|
||||
- 周报、季报、年报关注重点不同,不能只用一套普通表格。
|
||||
- 邮件投递需要可追踪:生成了什么、发给谁、是否成功、附件是什么。
|
||||
|
||||
因此本功能新增“财务报告编排员工”,负责把现有沉淀结果组织成管理层报告。
|
||||
|
||||
## 目标与非目标
|
||||
|
||||
### 目标
|
||||
|
||||
- 设计三类周期报告:
|
||||
- 周报:每周一上午投递上周财务经营与流程待办。
|
||||
- 季报:每季度首周投递上季度预算执行、结构变化和风险复盘。
|
||||
- 年报:每年一月投递上一年度费用经营、预算质量、制度经验和改进建议。
|
||||
- 报告输出为 PDF,包含图表、重点结论、异常解释和行动建议。
|
||||
- 邮件投递给财务管理人员,收件人来自系统设置、角色或配置名单。
|
||||
- 报告生成、PDF 渲染、邮件投递都写入数字员工工作记录。
|
||||
- 模板可版本化,后续可以调整样式和章节,不影响历史报告。
|
||||
|
||||
### 非目标
|
||||
|
||||
- 第一阶段不接入真实外部 BI 平台。
|
||||
- 第一阶段不要求复杂拖拽式模板编辑器。
|
||||
- 第一阶段不让数字员工自动修改预算、规则或审批结论。
|
||||
- 第一阶段不对外发送生产邮件,除非 SMTP 配置和测试收件人已确认。
|
||||
- 第一阶段不生成面向普通员工的个人账单报告,先聚焦财务管理层。
|
||||
|
||||
## 用户与场景
|
||||
|
||||
- **财务负责人**:阅读周报,知道本周费用规模、预算压力、异常单据和流程卡点。
|
||||
- **财务经理**:阅读季报,复盘部门费用结构、预算执行质量和高频风险。
|
||||
- **预算管理员**:从报告中看到预算使用率、超支预测、闲置预算和编制提醒。
|
||||
- **风控/审计人员**:从报告中看到风险观察、误报样本、制度缺口和重点复核对象。
|
||||
- **系统管理员**:查看报告任务是否按计划生成、渲染和发送。
|
||||
|
||||
## 报告周期与核心用途
|
||||
|
||||
### 周报
|
||||
|
||||
定位:经营驾驶舱 + 本周行动清单。
|
||||
|
||||
适合回答:
|
||||
|
||||
- 上周花了多少钱,多少单,环比是否异常。
|
||||
- 哪些部门、人员、费用类型最突出。
|
||||
- 本周有哪些待付款、待补材料、待审批和预算压力。
|
||||
- 数字员工发现了哪些风险线索,需要谁处理。
|
||||
|
||||
### 季报
|
||||
|
||||
定位:预算执行复盘 + 管理改进。
|
||||
|
||||
适合回答:
|
||||
|
||||
- 本季度预算使用是否健康。
|
||||
- 哪些部门长期超预算或预算闲置。
|
||||
- 哪些费用类型增长过快。
|
||||
- 员工画像和供应商画像中出现了什么稳定趋势。
|
||||
- 风险规则和制度条款哪里需要人工优化。
|
||||
|
||||
### 年报
|
||||
|
||||
定位:年度经营经验沉淀 + 下一年度管理建议。
|
||||
|
||||
适合回答:
|
||||
|
||||
- 全年费用结构和预算质量如何。
|
||||
- 哪些制度执行效果好,哪些制度经常缺引用或被反馈误报。
|
||||
- 哪些部门、岗位、费用类型需要来年重点管理。
|
||||
- 数字员工全年沉淀了哪些企业财务经验。
|
||||
- 下一年度预算编制、制度修订和风险模型优化建议是什么。
|
||||
|
||||
## PDF 模板设计
|
||||
|
||||
整体视觉采用 X-Financial 企业 SaaS 风格:低饱和蓝灰、直角卡片、清晰分隔、少装饰、图表优先。PDF 以 A4 纵向为主,关键图表允许横向宽图。
|
||||
|
||||
### 统一样式
|
||||
|
||||
- 字体:中文使用系统黑体或 Noto Sans CJK,数字使用等宽或 Inter 风格数字。
|
||||
- 主色:深蓝灰用于标题,财务蓝用于主指标,绿色表示健康,橙色表示预警,红色表示高风险。
|
||||
- 页眉:报告名称、周期、生成时间、数字员工名称。
|
||||
- 页脚:页码、数据窗口、保密提示。
|
||||
- 图表:柱状图、折线图、堆叠条、矩阵热力图、Top N 排行。
|
||||
- 每页结构:结论区在上,图表在中,解释和建议在下。
|
||||
|
||||
### 周报模板
|
||||
|
||||
建议 8-10 页:
|
||||
|
||||
1. 封面:报告周期、收件部门、生成时间。
|
||||
2. 管理摘要:3-5 条关键结论,突出金额、预算、风险和待办。
|
||||
3. 费用总览:报销金额、单数、人均费用、环比变化。
|
||||
4. 每日费用趋势:每日金额折线 + 每日单数柱状。
|
||||
5. 部门费用排行:Top 部门金额、单数、人均费用。
|
||||
6. 预算执行:预算使用率、预警预算池、待释放预占。
|
||||
7. 高额单据与个人排行:金额最高单据、金额最高个人、待付款金额。
|
||||
8. 流程待办:待审批、待补材料、待付款、待归档。
|
||||
9. 风险线索:高风险单据、材料异常、预算压力、重复票据。
|
||||
10. 本周行动清单:责任人、事项、建议动作、截止时间。
|
||||
|
||||
### 季报模板
|
||||
|
||||
建议 12-16 页:
|
||||
|
||||
1. 封面。
|
||||
2. 季度管理摘要。
|
||||
3. 季度费用结构:费用类型占比和季度变化。
|
||||
4. 部门预算执行矩阵:部门 x 费用类型预算使用率热力图。
|
||||
5. 预算偏差分析:超支、闲置、预占未释放、预测偏差。
|
||||
6. 部门经营画像:部门费用强度、流程质量、风险密度。
|
||||
7. 员工行为画像:高频报销、退回率、补材料率、异常波动。
|
||||
8. 供应商/商户画像:高频商户、集中度、异常关系。
|
||||
9. 风险观察复盘:确认率、误报率、高频风险信号。
|
||||
10. 制度执行复盘:制度条款命中、缺引用、冲突或过期条款。
|
||||
11. 数字员工工作成效:扫描次数、沉淀快照、提醒数量、关闭事项。
|
||||
12. 下季度管理建议:预算、制度、流程、风控四类建议。
|
||||
|
||||
### 年报模板
|
||||
|
||||
建议 18-24 页:
|
||||
|
||||
1. 封面。
|
||||
2. 年度管理摘要。
|
||||
3. 全年费用规模与趋势。
|
||||
4. 部门费用结构年度变化。
|
||||
5. 预算编制质量:预算准确率、调整频率、超支/闲置分布。
|
||||
6. 费用类型策略复盘:差旅、招待、办公、通信等。
|
||||
7. 流程效率年度复盘:提交、审批、付款、归档耗时。
|
||||
8. 员工画像年度沉淀:费用行为群组和变化。
|
||||
9. 供应商画像年度沉淀。
|
||||
10. 风险图谱年度复盘。
|
||||
11. 制度与规则效果:命中、误报、人工反馈和制度缺口。
|
||||
12. 数字员工年度工作记录:任务覆盖、报告、提醒、快照、风险线索。
|
||||
13. 下一年度预算编制建议。
|
||||
14. 下一年度制度优化建议。
|
||||
15. 下一年度风险治理建议。
|
||||
16. 附录:指标口径、数据窗口、样本限制。
|
||||
|
||||
## 邮件投递设计
|
||||
|
||||
### 收件人
|
||||
|
||||
收件人优先级:
|
||||
|
||||
1. 报告任务配置中的固定收件人。
|
||||
2. 系统设置中的 `default_receiver`、`notice_email` 或 `admin_email`。
|
||||
3. 具有财务管理、预算管理、风控审计角色的员工邮箱。
|
||||
|
||||
### 邮件内容
|
||||
|
||||
- 标题:`X-Financial 财务周报 | 2026-05-25 至 2026-05-31`
|
||||
- 正文:
|
||||
- 报告摘要 3 条。
|
||||
- 关键指标 4 个。
|
||||
- 待处理行动数量。
|
||||
- PDF 附件。
|
||||
- 系统内报告详情链接。
|
||||
|
||||
### 投递追踪
|
||||
|
||||
每次投递写入数字员工运行记录:
|
||||
|
||||
- 报告类型:weekly / quarterly / annual。
|
||||
- 报告周期。
|
||||
- PDF 文件路径或存储 key。
|
||||
- 收件人列表。
|
||||
- 邮件发送状态。
|
||||
- 失败原因。
|
||||
- 重试次数。
|
||||
|
||||
## 后端方案
|
||||
|
||||
### 新增服务
|
||||
|
||||
- `finance_report_context.py`:聚合财务看板、预算、风险、画像、提醒、数字员工运行记录。
|
||||
- `finance_report_template.py`:定义周报、季报、年报章节和图表配置。
|
||||
- `finance_report_renderer.py`:将报告上下文渲染为 HTML,再生成 PDF。
|
||||
- `finance_report_mailer.py`:读取 SMTP 配置并发送邮件。
|
||||
- `finance_report_scheduler.py`:按周、季、年触发报告生成。
|
||||
- `digital_employee_finance_report_task.py`:数字员工任务编排入口。
|
||||
|
||||
### 数据来源
|
||||
|
||||
- `expense_claims`、`expense_claim_items`:费用、单据、部门、状态。
|
||||
- `budget_allocations`、`budget_transactions`、`budget_reservations`:预算执行。
|
||||
- `risk_observations`:风险观察和复核结果。
|
||||
- `employee_behavior_profile_snapshots`:员工画像。
|
||||
- `agent_runs`、`agent_tool_calls`:数字员工工作记录、提醒扫描、看板快照。
|
||||
- `settings`:SMTP 和默认收件人配置。
|
||||
|
||||
### 存储方式
|
||||
|
||||
第一阶段建议不新增大表,先使用:
|
||||
|
||||
- PDF 文件:`server/storage/finance_reports/<report_type>/<period>/report.pdf`
|
||||
- 元数据:写入 `agent_runs.route_json.report_delivery`
|
||||
|
||||
如果后续需要报告列表、重发、下载和归档,再新增 `finance_reports` 表。
|
||||
|
||||
## 前端方案
|
||||
|
||||
第一阶段只做必要入口:
|
||||
|
||||
- 数字员工工作记录中显示“财务周报/季报/年报生成”。
|
||||
- 报告运行详情显示摘要、收件人、PDF 路径和发送状态。
|
||||
- 系统设置保留 SMTP 配置,不新增复杂模板编辑器。
|
||||
|
||||
第二阶段新增报告中心:
|
||||
|
||||
- 报告列表:类型、周期、生成时间、发送状态。
|
||||
- 报告详情:PDF 预览、摘要、指标、收件人。
|
||||
- 手动生成:选择周期和收件人后触发数字员工。
|
||||
- 重发邮件:仅对已有 PDF 重发,不重复计算。
|
||||
|
||||
## 数字员工新增能力
|
||||
|
||||
### 必做技能
|
||||
|
||||
1. **财务报告编排**
|
||||
- 把看板、预算、风险、画像和提醒整合为报告上下文。
|
||||
- 输出 PDF 和邮件摘要。
|
||||
|
||||
2. **预算偏差解释**
|
||||
- 对预算超支、闲置、预占未释放做原因归因。
|
||||
- 输出部门、费用类型和责任人视角建议。
|
||||
|
||||
3. **流程效率复盘**
|
||||
- 沉淀审批、付款、归档耗时。
|
||||
- 找出长期卡点和责任角色。
|
||||
|
||||
4. **制度缺口复盘**
|
||||
- 汇总风险观察中缺少制度依据的情况。
|
||||
- 提示制度管理员补齐条款,不自动改规则。
|
||||
|
||||
5. **报告投递与回执跟踪**
|
||||
- 记录邮件是否发出、是否失败、是否需要重试。
|
||||
|
||||
### 可逐步挖掘的高价值技能
|
||||
|
||||
- **费用结构漂移检测**:发现某部门费用类型占比突然变化。
|
||||
- **预算预测与预警**:基于当前消耗预测季度末是否超支。
|
||||
- **重复报销关系挖掘**:从员工、商户、发票、地点关系中找重复模式。
|
||||
- **供应商集中度监控**:识别费用过度集中到少数商户或供应商。
|
||||
- **部门横向对标**:同规模部门人均费用、退回率、补材料率对比。
|
||||
- **制度执行热力图**:哪些制度条款最常命中,哪些最常被人工否定。
|
||||
- **数字员工建议命中率复盘**:数字员工提醒、风险线索和人工处理结果之间的闭环。
|
||||
- **异常趋势早期信号**:在风险尚未形成前发现金额、频次、提交时间的异常变化。
|
||||
|
||||
## 算法与公式
|
||||
|
||||
### 周报异常评分
|
||||
|
||||
$$
|
||||
weekly\_alert\_score = 0.35 \times spend\_change + 0.25 \times budget\_pressure + 0.25 \times risk\_density + 0.15 \times process\_delay
|
||||
$$
|
||||
|
||||
其中:
|
||||
|
||||
- `spend_change`:本周费用环比变化归一化值。
|
||||
- `budget_pressure`:预算使用率或预测超支风险。
|
||||
- `risk_density`:风险单据金额 / 报销总金额。
|
||||
- `process_delay`:逾期待处理事项占比。
|
||||
|
||||
### 预算预测
|
||||
|
||||
$$
|
||||
predicted\_usage = current\_usage + \frac{current\_usage}{elapsed\_days} \times remaining\_days
|
||||
$$
|
||||
|
||||
当 `predicted_usage > budget_limit` 时,报告标记为预算超支预测。
|
||||
|
||||
### 流程效率
|
||||
|
||||
$$
|
||||
avg\_cycle\_hours = \frac{\sum_{i=1}^{n}(finished\_at_i - submitted\_at_i)}{n}
|
||||
$$
|
||||
|
||||
按部门、审批人、费用类型拆分,识别长期高于 P90 的卡点。
|
||||
|
||||
### 报告优先级
|
||||
|
||||
$$
|
||||
section\_priority = 0.4 \times amount\_impact + 0.3 \times risk\_impact + 0.2 \times recurrence + 0.1 \times management\_urgency
|
||||
$$
|
||||
|
||||
用于决定管理摘要中展示哪些结论。
|
||||
|
||||
## 测试方案
|
||||
|
||||
- 后端单元测试:报告上下文聚合、模板章节生成、指标计算。
|
||||
- PDF 渲染测试:生成 HTML 和 PDF,检查页数、标题、图表占位和附件存在。
|
||||
- 邮件测试:使用 mock SMTP,验证标题、收件人、正文和附件。
|
||||
- 调度测试:周报、季报、年报触发时间和重复执行保护。
|
||||
- 数字员工运行记录测试:确认报告生成和邮件投递写入 `agent_runs`。
|
||||
- 容器验证:在 `x-financial-main:/app` 内运行定向 pytest,60s 超时。
|
||||
- 手工验证:生成一份周报 PDF,检查图文布局、中文显示、金额格式和页码。
|
||||
|
||||
## 指标与验收
|
||||
|
||||
- 可以生成一份周报 PDF,包含摘要、趋势图、部门排行、预算、风险和行动清单。
|
||||
- PDF 文件路径写入数字员工运行记录。
|
||||
- 邮件 mock 测试能验证附件发送。
|
||||
- SMTP 未配置时任务不失败,降级为“生成成功、投递待配置”。
|
||||
- 周报、季报、年报模板均有独立章节定义。
|
||||
- 报告中的单号、部门、金额、状态来自真实数据库聚合。
|
||||
- 数字员工看板能看到报告生成任务和结果摘要。
|
||||
|
||||
## 风险与开放问题
|
||||
|
||||
- PDF 渲染依赖中文字体和浏览器/渲染库环境,必须在容器内验证。
|
||||
- 真实 SMTP 投递涉及外部邮件服务器,需要先用测试收件人验证。
|
||||
- 若后续要求报告下载、重发、审阅状态和历史归档,建议新增 `finance_reports` 表。
|
||||
- 季报和年报需要更稳定的画像和风险反馈数据,否则前期只能展示模拟或有限结论。
|
||||
- 图表渲染要避免依赖前端 ECharts 截图,优先后端生成可控 SVG/HTML 图表。
|
||||
@@ -1,80 +0,0 @@
|
||||
# 数字员工财务报告体系 TODO
|
||||
|
||||
更新日期:2026-06-02
|
||||
|
||||
## 阶段一:调研与契约
|
||||
|
||||
- [x] 梳理现有财务看板、预算、风险、画像、提醒扫描和数字员工运行记录接口字段。[CONCEPT: 数据来源] 证据:`finance_report_context.py` 已聚合 `FinanceDashboardService`、`RiskObservation`、`EmployeeBehaviorProfileSnapshot`、`AgentRun`。
|
||||
- [x] 梳理系统设置中的 SMTP 配置字段和默认收件人来源。[CONCEPT: 邮件投递设计] 证据:`finance_report_mailer.py` 已读取 `SystemSetting` 和 `SystemSettingSecret`。
|
||||
- [x] 定义报告任务类型:`weekly_finance_report`、`quarterly_finance_report`、`annual_finance_report`。[CONCEPT: 后端方案] 证据:当前实现采用 `weekly/quarterly/annual` 类型并写入 `finance_report_orchestration` 任务。
|
||||
- [x] 定义数字员工任务 code、技能名称、输出格式和调度周期。[CONCEPT: 数字员工新增能力] 证据:`task.hermes.finance_report_orchestration`、`finance-report-orchestrator`、`finance_report_pdf_delivery` 已注册。
|
||||
- [x] 定义报告上下文 schema,覆盖摘要、指标、图表、行动清单、投递结果。[CONCEPT: 后端方案] 证据:`DigitalEmployeeFinanceReportTaskService._result_payload()` 已输出 `summary/insights/action_items/pdf/delivery`。
|
||||
|
||||
## 阶段二:模板与样式
|
||||
|
||||
- [x] 新增周报模板章节配置,包含摘要、费用趋势、部门排行、预算、高额单据、流程待办、风险线索和行动清单。[CONCEPT: 周报模板] 证据:`finance_report_renderer.py` 已输出周报 HTML/PDF 章节。
|
||||
- [ ] 新增季报模板章节配置,包含预算执行矩阵、员工画像、供应商画像、风险复盘和下季度建议。[CONCEPT: 季报模板]
|
||||
- [ ] 新增年报模板章节配置,包含年度费用、预算质量、流程效率、制度效果和下一年度建议。[CONCEPT: 年报模板]
|
||||
- [x] 设计统一 PDF 主题变量:字体、颜色、页眉、页脚、图表色板、金额格式。[CONCEPT: 统一样式] 证据:`FinanceReportRenderer.render_html()` 与 `SimpleFinancePdfWriter` 已定义报告样式和图表表现。
|
||||
- [x] 准备 HTML 到 PDF 的最小渲染样例,验证中文字体、页码、分页和图表展示。[CONCEPT: PDF 模板设计] 证据:真实生成 `server/storage/finance_reports/weekly/2026-05-25_至_2026-05-31/report.pdf`,PDF 头为 `%PDF`。
|
||||
|
||||
## 阶段三:后端报告上下文
|
||||
|
||||
- [x] 新增 `finance_report_context.py`,聚合财务看板、预算、风险、画像、提醒和数字员工运行记录。[CONCEPT: 后端方案] 证据:服务文件已新增并通过测试。
|
||||
- [x] 实现周报上下文计算,输出上周金额、单数、环比、预算压力、风险线索和行动清单。[CONCEPT: 周报] 证据:脚本生成周报摘要 `30 单 / ¥135,058 / 5 项行动`。
|
||||
- [ ] 实现季报上下文计算,输出季度预算偏差、部门矩阵、画像复盘和风险反馈。[CONCEPT: 季报]
|
||||
- [ ] 实现年报上下文计算,输出年度趋势、预算质量、制度执行和数字员工沉淀成果。[CONCEPT: 年报]
|
||||
- [ ] 实现异常评分、预算预测、流程效率和章节优先级公式。[CONCEPT: 算法与公式]
|
||||
|
||||
## 阶段四:PDF 渲染
|
||||
|
||||
- [x] 新增 `finance_report_template.py`,把上下文映射为章节、图表和建议文本。[CONCEPT: 后端方案] 证据:第一版模板逻辑内聚在 `finance_report_renderer.py`,后续如需复杂模板再拆文件。
|
||||
- [x] 新增 `finance_report_renderer.py`,把模板渲染为 HTML。[CONCEPT: 后端方案] 证据:已生成 `report.html`。
|
||||
- [x] 接入 PDF 渲染方案,输出到 `server/storage/finance_reports/<type>/<period>/report.pdf`。[CONCEPT: 存储方式] 证据:已生成 `finance_reports/weekly/2026-05-25_至_2026-05-31/report.pdf`。
|
||||
- [x] 生成周报 PDF 样例,手工检查封面、摘要、图表、行动清单和页脚。[CONCEPT: 指标与验收] 证据:容器内确认 PDF 文件存在且以 `%PDF` 开头。
|
||||
- [ ] 渲染失败时保留 HTML 和错误信息,写入数字员工运行记录。[CONCEPT: 风险与开放问题]
|
||||
|
||||
## 阶段五:邮件投递
|
||||
|
||||
- [x] 新增 `finance_report_mailer.py`,读取 SMTP 配置和默认收件人。[CONCEPT: 邮件投递设计] 证据:已联动系统设置 SMTP 字段和加密密码。
|
||||
- [x] SMTP 未配置时降级为“报告生成成功、投递待配置”。[CONCEPT: 指标与验收] 证据:真实脚本返回 `pending_configuration`,原因 `smtp_password` 缺失。
|
||||
- [ ] 使用 mock SMTP 测试邮件标题、正文、收件人和 PDF 附件。[CONCEPT: 测试方案]
|
||||
- [x] 记录邮件投递状态、失败原因、重试次数和收件人列表。[CONCEPT: 投递追踪] 证据:`agent_runs.route_json.report_delivery.delivery` 已记录收件人、主题、状态和失败原因。
|
||||
- [ ] 支持手动重发已有 PDF,不重复计算报告上下文。[CONCEPT: 前端方案]
|
||||
|
||||
## 阶段六:数字员工任务与调度
|
||||
|
||||
- [x] 新增 `digital_employee_finance_report_task.py`,作为报告编排员工入口。[CONCEPT: 后端方案] 证据:服务已生成报告、PDF 和投递结果。
|
||||
- [x] 新增或扩展报告调度器,支持每周、每季、每年执行。[CONCEPT: 报告周期与核心用途] 证据:`finance_report_scheduler.py` 已按周、季、年触发并做当天去重。
|
||||
- [x] 将报告生成写入 `agent_runs` 和 `agent_tool_calls`。[CONCEPT: 邮件投递设计] 证据:`run_f137ec8112cd44eb` 成功记录报告结果。
|
||||
- [x] 在数字员工技能列表中新增“财务报告编排”技能。[CONCEPT: 数字员工新增能力] 证据:技能中心同步后查询到 `task.hermes.finance_report_orchestration`。
|
||||
- [x] 在数字员工工作记录中展示报告生成、PDF 路径、投递状态和摘要。[CONCEPT: 前端方案] 证据:当前通过 `agent_runs.route_json.report_delivery` 暴露,前端详情可读取。
|
||||
|
||||
## 阶段七:报告中心增强
|
||||
|
||||
- [ ] 评估是否新增 `finance_reports` 表,用于报告列表、下载、重发、审阅状态和历史归档。[CONCEPT: 存储方式]
|
||||
- [ ] 新增报告列表接口,按类型、周期、生成状态筛选。[CONCEPT: 前端方案]
|
||||
- [ ] 新增报告详情接口,返回摘要、收件人、PDF 下载地址和投递记录。[CONCEPT: 前端方案]
|
||||
- [ ] 前端新增报告中心页面或数字员工详情页入口。[CONCEPT: 前端方案]
|
||||
- [ ] 支持手动生成报告,选择周期和测试收件人。[CONCEPT: 前端方案]
|
||||
|
||||
## 阶段八:高价值挖掘技能
|
||||
|
||||
- [ ] 费用结构漂移检测:识别部门费用类型占比突变。[CONCEPT: 可逐步挖掘的高价值技能]
|
||||
- [ ] 预算预测与预警:预测季度末超支风险。[CONCEPT: 可逐步挖掘的高价值技能]
|
||||
- [ ] 重复报销关系挖掘:识别员工、商户、发票、地点的重复模式。[CONCEPT: 可逐步挖掘的高价值技能]
|
||||
- [ ] 供应商集中度监控:识别费用过度集中到少数商户或供应商。[CONCEPT: 可逐步挖掘的高价值技能]
|
||||
- [ ] 部门横向对标:同规模部门人均费用、退回率、补材料率对比。[CONCEPT: 可逐步挖掘的高价值技能]
|
||||
- [ ] 制度执行热力图:统计条款命中、缺引用和人工否定。[CONCEPT: 可逐步挖掘的高价值技能]
|
||||
- [ ] 数字员工建议命中率复盘:把提醒、风险线索和人工处理结果闭环。[CONCEPT: 可逐步挖掘的高价值技能]
|
||||
- [ ] 异常趋势早期信号:发现未形成风险前的金额、频次和提交时间异常。[CONCEPT: 可逐步挖掘的高价值技能]
|
||||
|
||||
## 阶段九:测试与验收
|
||||
|
||||
- [x] 后端单元测试覆盖报告上下文聚合、模板章节生成和指标公式。[CONCEPT: 测试方案] 证据:`test_finance_report_task.py` 覆盖报告生成和摘要。
|
||||
- [x] PDF 渲染测试覆盖中文字体、页数、标题、图表占位和文件存在。[CONCEPT: 测试方案] 证据:测试确认 PDF 文件存在且以 `%PDF` 开头。
|
||||
- [ ] 邮件 mock 测试覆盖标题、正文、收件人和附件。[CONCEPT: 测试方案]
|
||||
- [ ] 调度测试覆盖周报、季报、年报触发时间和重复执行保护。[CONCEPT: 测试方案]
|
||||
- [x] 容器内运行定向测试,命令使用 `docker exec -w /app -e SERVER_VENV_DIR=/tmp/x-financial-server-venv x-financial-main ...`,60s 超时。[CONCEPT: 测试方案] 证据:`pytest -q server/tests/test_finance_report_task.py server/tests/test_digital_employee_skill_catalog.py` 4 passed。
|
||||
- [x] 生成真实周报 PDF 并检查最终用户可见效果。[CONCEPT: 指标与验收] 证据:`server/scripts/generate_finance_report.py --type weekly --dry-run-email` 生成真实周报。
|
||||
- [x] 验证数字员工看板能看到报告任务和投递结果。[CONCEPT: 指标与验收] 证据:运行记录中已有 `finance_report_orchestration` 和 `report_delivery`。
|
||||
@@ -1,227 +0,0 @@
|
||||
# 数字员工财务经验沉淀与定时提醒概念文档
|
||||
|
||||
更新日期:2026-06-02
|
||||
|
||||
## 功能一句话
|
||||
|
||||
把数字员工定位为后台财务数据分析员:定时沉淀企业财务经验,周期性生成分析报告,并在审批、预算、出差申请和报销流程中生成可追踪的提醒建议。
|
||||
|
||||
## 背景与问题
|
||||
|
||||
当前数字员工已经具备技能目录、财务看板快照和员工行为画像扫描能力,但业务价值仍偏弱:
|
||||
|
||||
- 技能列表数量多,但多数只是能力定义,缺少持续沉淀和行动产出。
|
||||
- 员工画像已有数据,但如果不持续沉淀,系统不会随企业数据变多而变聪明。
|
||||
- 财务流程中存在大量需要定时推动的事项,例如领导审批、预算编制、出差申请到期、报销补材料和归档。
|
||||
- 现在缺少统一的后台提醒扫描结果,无法证明数字员工每天发现了哪些待处理事项、提醒了谁、为什么提醒。
|
||||
|
||||
因此本功能把数字员工拆成三条主线:
|
||||
|
||||
- **行为沉淀技能**:每天小颗粒沉淀费用、预算、单据、流程、画像经验。
|
||||
- **定时提醒技能**:按时间窗口扫描待办事项,生成面向责任人的提醒清单。
|
||||
- **周期报告技能**:读取沉淀结果和提醒效果,形成企业财务经验报告。
|
||||
|
||||
## 目标与非目标
|
||||
|
||||
### 目标
|
||||
|
||||
- 建立数字员工“后台分析员”定位,不再把全部技能包装成前台执行能力。
|
||||
- 收敛技能体系为行为沉淀、定时提醒、周期报告三类。
|
||||
- 第一阶段落地一个真实可运行的 **定时提醒扫描任务**。
|
||||
- 提醒扫描使用现有业务数据,不新增数据库结构,结果写入 `agent_runs` 和 `agent_tool_calls`。
|
||||
- 提醒扫描至少覆盖:
|
||||
- 待审批单据提醒。
|
||||
- 预算编制/预算缺口提醒。
|
||||
- 出差申请到期后待报销提醒。
|
||||
- 报销逾期、补材料、付款/归档提醒。
|
||||
- 数字员工看板能够看到提醒扫描的运行记录和提醒产出数量。
|
||||
|
||||
### 非目标
|
||||
|
||||
- 第一阶段不做站内信、邮件、短信或企业微信真实投递。
|
||||
- 第一阶段不新增提醒表、已读表、重复提醒去重表等数据库结构。
|
||||
- 第一阶段不替代审批、付款、预算编制和报销操作,只生成提醒建议。
|
||||
- 第一阶段不让数字员工自动修改单据状态、预算状态或审批结果。
|
||||
- 第一阶段不做完整报告页面,只把提醒报告结构化写入运行记录。
|
||||
|
||||
## 用户与场景
|
||||
|
||||
- **部门领导**:每天收到待审批单据汇总,知道待审数量、最高金额、最长等待时间。
|
||||
- **预算管理员**:在预算周期临近或预算池缺失时收到编制/补齐提醒。
|
||||
- **出差员工**:出差申请结束后未报销时收到报销或延长申请提醒。
|
||||
- **财务人员**:看到报销逾期、补材料、付款、归档等流程卡点。
|
||||
- **财务负责人**:周期性查看提醒扫描报告,判断哪些流程经常阻塞。
|
||||
- **系统管理员**:在数字员工看板查看提醒任务是否稳定运行。
|
||||
|
||||
## 功能能力
|
||||
|
||||
### 行为沉淀技能
|
||||
|
||||
后续应逐步沉淀以下经验快照:
|
||||
|
||||
- 费用结构基线:部门、费用类型、月份、单数、金额、均值、P90。
|
||||
- 预算执行偏差:使用率、闲置率、超支风险、预测偏差。
|
||||
- 报销行为画像:员工/部门报销频率、金额区间、退回率、补材料率。
|
||||
- 单据质量经验:缺附件、发票异常、金额不一致、退回原因。
|
||||
- 流程效率经验:提交到审批、审批到付款、付款到归档的耗时。
|
||||
- 制度执行经验:制度条款命中频率、人工否定频率、制度缺口。
|
||||
|
||||
### 定时提醒技能
|
||||
|
||||
第一阶段实现 `digital_employee_reminder_scan`,生成统一提醒报告:
|
||||
|
||||
- `approval_pending`:待审批提醒。
|
||||
- `budget_compilation`:预算编制/预算池缺口提醒。
|
||||
- `travel_application_expiry`:出差申请已结束但未报销提醒。
|
||||
- `reimbursement_overdue`:报销逾期、补材料、待付款、待归档提醒。
|
||||
|
||||
提醒报告只写入数字员工运行记录,结构包含:
|
||||
|
||||
- 扫描时间和窗口。
|
||||
- 每类提醒数量。
|
||||
- 每个收件人的提醒摘要。
|
||||
- 关联单据、金额、最长等待时间、建议动作。
|
||||
- 是否需要人工处理。
|
||||
|
||||
### 周期报告技能
|
||||
|
||||
后续在沉淀和提醒任务稳定后生成:
|
||||
|
||||
- 每日财务经营摘要。
|
||||
- 周度流程效率复盘。
|
||||
- 月度预算执行复盘。
|
||||
- 半年度企业财务经验报告。
|
||||
|
||||
## 方案设计
|
||||
|
||||
### 后端
|
||||
|
||||
第一阶段新增三个后端模块:
|
||||
|
||||
- `digital_employee_reminder_task.py`:执行提醒扫描,写入 `AgentRun`。
|
||||
- `digital_employee_reminder_scheduler.py`:后台调度器,默认每天 02:00 扫描,可配置首次延迟用于开发验证。
|
||||
- `digital_employee_dashboard.py`:扩展任务类型和指标,让看板统计提醒产出。
|
||||
|
||||
提醒扫描复用现有表:
|
||||
|
||||
- `expense_claims`:报销单和费用申请单。
|
||||
- `employees`:员工、直属领导、角色。
|
||||
- `budget_allocations`:预算池。
|
||||
- `agent_runs` / `agent_tool_calls`:数字员工运行记录。
|
||||
|
||||
### 数据输出结构
|
||||
|
||||
运行记录中的 `route_json.report` 使用如下结构:
|
||||
|
||||
```json
|
||||
{
|
||||
"title": "数字员工定时提醒扫描报告",
|
||||
"generatedAt": "2026-06-02T02:00:00+08:00",
|
||||
"windowDays": 14,
|
||||
"totals": {
|
||||
"recipientCount": 8,
|
||||
"reminderCount": 23,
|
||||
"approvalPendingCount": 7,
|
||||
"budgetReminderCount": 4,
|
||||
"travelApplicationReminderCount": 5,
|
||||
"reimbursementOverdueCount": 7
|
||||
},
|
||||
"recipients": [
|
||||
{
|
||||
"recipientId": "emp-001",
|
||||
"recipientName": "张三",
|
||||
"recipientRole": "manager",
|
||||
"reminders": [
|
||||
{
|
||||
"type": "approval_pending",
|
||||
"priority": "high",
|
||||
"title": "你有 3 笔报销单待审批",
|
||||
"action": "请在今日处理审批待办",
|
||||
"relatedDocuments": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 前端
|
||||
|
||||
第一阶段不新增独立页面。数字员工看板通过已有最近运行记录展示:
|
||||
|
||||
- 任务名称:定时提醒扫描。
|
||||
- 产出数量:提醒数量。
|
||||
- 最近摘要:提醒了多少人、多少条事项。
|
||||
|
||||
后续可在数字员工工作记录详情中扩展“提醒报告详情”。
|
||||
|
||||
## 算法与公式
|
||||
|
||||
### 提醒优先级
|
||||
|
||||
提醒优先级由等待天数、金额和业务类型决定:
|
||||
|
||||
$$
|
||||
priority\_score = 0.45 \times wait\_score + 0.35 \times amount\_score + 0.20 \times type\_score
|
||||
$$
|
||||
|
||||
其中:
|
||||
|
||||
- `wait_score = min(wait_days / threshold_days, 1)`
|
||||
- `amount_score = min(amount / high_amount_threshold, 1)`
|
||||
- `type_score`:审批、预算、出差、报销流程分别给定基础分。
|
||||
|
||||
优先级映射:
|
||||
|
||||
$$
|
||||
priority =
|
||||
\begin{cases}
|
||||
high, & priority\_score \ge 0.75 \\
|
||||
medium, & 0.45 \le priority\_score < 0.75 \\
|
||||
low, & priority\_score < 0.45
|
||||
\end{cases}
|
||||
$$
|
||||
|
||||
### 待审批等待天数
|
||||
|
||||
$$
|
||||
wait\_days = floor((now - submitted\_at) / 86400)
|
||||
$$
|
||||
|
||||
如果 `submitted_at` 为空,则使用 `updated_at` 或 `created_at` 降级计算。
|
||||
|
||||
### 预算缺口识别
|
||||
|
||||
当前阶段使用预算池存在性和周期作为提醒依据:
|
||||
|
||||
$$
|
||||
budget\_gap = active\_allocation\_count = 0
|
||||
$$
|
||||
|
||||
当当前年度/期间没有有效预算池,或预算池处于非 active/published 状态时,生成预算编制提醒。
|
||||
|
||||
## 测试方案
|
||||
|
||||
- 后端单元测试:构造员工、领导、报销单、申请单和预算池,验证提醒报告数量与收件人。
|
||||
- 看板聚合测试:构造 `digital_employee_reminder_scan` 运行记录,验证 `reminders` 指标被统计。
|
||||
- 调度器测试:验证 scheduler 能调用任务服务,不重复启动。
|
||||
- 容器验证:在 `x-financial-main:/app` 内运行定向 pytest,60s 超时。
|
||||
- 运行时验证:重启容器后查询 `agent_runs`,确认提醒扫描记录成功生成。
|
||||
- HTTP 验证:调用 `/api/v1/analytics/digital-employee-dashboard`,确认任务分布包含定时提醒扫描。
|
||||
|
||||
## 指标与验收
|
||||
|
||||
- `agent_runs` 中出现 `task_type=digital_employee_reminder_scan` 的成功运行。
|
||||
- 工具响应包含 `recipient_count`、`reminder_count` 和四类提醒计数。
|
||||
- 数字员工看板 `businessOutputs` 计入提醒数量。
|
||||
- 最近运行记录展示“定时提醒扫描”。
|
||||
- 定向测试通过。
|
||||
- 不新增数据库结构,不改变现有单据状态。
|
||||
|
||||
## 风险与开放问题
|
||||
|
||||
- 第一阶段只生成提醒报告,不做真实消息投递;后续需要站内信/邮件/企业微信时再新增消息模型。
|
||||
- 当前预算编制状态模型还不完整,第一阶段只能基于预算池缺口和期间判断。
|
||||
- 出差申请到期依赖申请单中的 `application_detail.time`,如果历史数据缺失,只能降级使用 `occurred_at`。
|
||||
- 审批责任人目前主要通过员工直属领导推断,复杂动态审批流需要后续对接审批路由结果。
|
||||
- 如果后续需要“已读/已处理/重复提醒抑制”,必须新增提醒表或消息表,并进行数据库迁移确认。
|
||||
@@ -1,39 +0,0 @@
|
||||
# 数字员工财务经验沉淀与定时提醒开发 TODO
|
||||
|
||||
## 阶段一:调研与文档
|
||||
|
||||
- [x] 梳理现有数字员工技能、画像扫描、财务看板快照和看板聚合链路。[CONCEPT: 背景与问题] 证据:已核对 `agent_foundation_digital_employee_tasks.py`、`digital_employee_dashboard.py`、`employee_profile_scan_task.py`。
|
||||
- [x] 梳理审批、预算、出差申请和报销单模型字段。[CONCEPT: 方案设计] 证据:已核对 `approval.py`、`budget.py`、`financial_record.py`、`user_agent_application.py`。
|
||||
- [x] 明确第一阶段不新增数据库结构,只用 `agent_runs` 和 `agent_tool_calls` 保存提醒扫描报告。[CONCEPT: 目标与非目标] 证据:`CONCEPT.md` 已写明。
|
||||
- [x] 创建概念文档和开发 TODO。[CONCEPT: 全文] 证据:本目录 `CONCEPT.md` 与 `TODO.md`。
|
||||
|
||||
## 阶段二:后端提醒扫描任务
|
||||
|
||||
- [x] 新增 `digital_employee_reminder_task.py`,定义 `DigitalEmployeeReminderTaskService`。[CONCEPT: 后端] 证据:新增服务文件并通过 ruff。
|
||||
- [x] 实现待审批提醒扫描,按直属领导聚合待审批单据。[CONCEPT: 定时提醒技能] 证据:`test_digital_employee_reminder_task.py` 覆盖 `approval_pending`。
|
||||
- [x] 实现预算编制/预算缺口提醒,按当前年度和期间识别预算池缺口。[CONCEPT: 定时提醒技能] 证据:`test_digital_employee_reminder_task.py` 覆盖 `budget_compilation`。
|
||||
- [x] 实现出差申请到期提醒,识别已结束但未报销或未关闭的申请单。[CONCEPT: 定时提醒技能] 证据:`test_digital_employee_reminder_task.py` 覆盖 `travel_application_expiry`。
|
||||
- [x] 实现报销逾期/补材料/付款/归档提醒,识别流程卡点。[CONCEPT: 定时提醒技能] 证据:`test_digital_employee_reminder_task.py` 覆盖 `reimbursement_overdue`。
|
||||
- [x] 将提醒报告写入 `AgentRun` 和 `AgentToolCall`,包含 `recipient_count`、`reminder_count` 和分类计数。[CONCEPT: 数据输出结构] 证据:任务服务测试读取返回 summary 与 report。
|
||||
|
||||
## 阶段三:调度与看板
|
||||
|
||||
- [x] 新增 `digital_employee_reminder_scheduler.py`,默认每天 02:00 扫描,支持开发环境首次延迟运行。[CONCEPT: 后端] 证据:新增调度器并通过 ruff。
|
||||
- [x] 在 `main.py` 生命周期中启动和关闭提醒调度器。[CONCEPT: 后端] 证据:`main.py` 已接入 scheduler start/shutdown。
|
||||
- [x] 扩展 `DigitalEmployeeDashboardService`,识别 `digital_employee_reminder_scan`。[CONCEPT: 前端] 证据:看板聚合测试覆盖 task type。
|
||||
- [x] 看板指标增加提醒产出计数,最近运行记录显示“定时提醒扫描”。[CONCEPT: 指标与验收] 证据:`test_digital_employee_dashboard_service.py` 覆盖 `reminders` 和 `businessOutputs`。
|
||||
|
||||
## 阶段四:测试与验证
|
||||
|
||||
- [x] 新增后端单元测试,验证四类提醒的收件人、数量和摘要。[CONCEPT: 测试方案] 证据:`server/tests/test_digital_employee_reminder_task.py`。
|
||||
- [x] 新增数字员工看板聚合测试,验证提醒数量进入 `businessOutputs`。[CONCEPT: 测试方案] 证据:`server/tests/test_digital_employee_dashboard_service.py`。
|
||||
- [x] 在容器内运行 ruff:`docker exec -w /app -e SERVER_VENV_DIR=/tmp/x-financial-server-venv x-financial-main /tmp/x-financial-server-venv/bin/python -m ruff check <changed-files>`。[CONCEPT: 测试方案] 证据:All checks passed。
|
||||
- [x] 在容器内运行定向 pytest,超时 60s,验证提醒任务和看板聚合。[CONCEPT: 测试方案] 证据:`5 passed in 3.39s`。
|
||||
- [x] 重启 `x-financial-main`,查询 `agent_runs` 确认提醒扫描运行记录成功生成。[CONCEPT: 指标与验收] 证据:`run_4c3a2b847fae4ada` succeeded,提醒 47 人,生成 403 条事项。
|
||||
- [x] 调用 `/api/v1/analytics/digital-employee-dashboard`,确认任务分布包含定时提醒扫描。[CONCEPT: 指标与验收] 证据:HTTP 200,`reminders=403`,任务分布包含 `digital_employee_reminder_scan`。
|
||||
|
||||
## 后续阶段:消息投递闭环
|
||||
|
||||
- [ ] 评估是否新增提醒消息表、已读状态和重复提醒抑制策略。[CONCEPT: 风险与开放问题]
|
||||
- [ ] 设计站内信、邮件或企业微信投递通道。[CONCEPT: 非目标]
|
||||
- [ ] 设计提醒处理结果回流,用于沉淀“哪些提醒真正有效”。[CONCEPT: 行为沉淀技能]
|
||||
@@ -1,127 +0,0 @@
|
||||
# 申请交通费用自动预估概念文档
|
||||
|
||||
## 功能一句话
|
||||
|
||||
在费用申请预览阶段,系统自动生成交通票价 mock 估算,并叠加现有住宿与补助标准,形成申请预估总费用,替代用户手动填写预估金额。
|
||||
|
||||
## 背景与问题
|
||||
|
||||
当前申请流程要求用户补充“用户预估费用”。对差旅申请来说,用户在申请阶段往往只能确认出行方式、目的地和天数,交通票价又暂时缺少正式票务接口支持,因此让用户手填金额会降低流程顺畅度。
|
||||
|
||||
本期先用系统估算补齐申请金额:
|
||||
|
||||
- 交通费用:按火车、飞机、轮船三类 mock 票价生成稳定估算。
|
||||
- 住宿与补助:前端申请预览继续调用现有差旅规则测算,复用住宿上限和补助标准。
|
||||
- 后端对话:当没有前端预览上下文时,用同口径的兜底估算生成金额,避免选择交通方式后继续追问金额。
|
||||
|
||||
## 目标与非目标
|
||||
|
||||
目标:
|
||||
|
||||
- 申请预览不再要求用户手动填写预估费用。
|
||||
- 用户选择火车、飞机或轮船后,系统能生成交通费用估算。
|
||||
- 预估总费用 = mock 交通费 + 住宿标准小计 + 补助标准小计。
|
||||
- 预览、提交文本和后端对话统一展示“系统预估费用”。
|
||||
- 保留用户明确输入金额时的兼容能力,不破坏历史提交链路。
|
||||
|
||||
非目标:
|
||||
|
||||
- 本期不接入真实机票、火车票、船票接口。
|
||||
- 本期不做票价实时波动、余票、舱等、席别、折扣和路线中转算法。
|
||||
- 本期不把 mock 估算作为最终报销金额,报销阶段仍应结合真实票据复核。
|
||||
|
||||
## 用户与场景
|
||||
|
||||
主要用户:
|
||||
|
||||
- 员工:在“申请/事前审批”环节快速发起差旅费用申请。
|
||||
- 财务/审批人:查看申请金额是否由系统按标准测算生成。
|
||||
|
||||
典型场景:
|
||||
|
||||
1. 用户输入“去上海出差 3 天,火车”。
|
||||
2. 系统识别地点、天数、出行方式。
|
||||
3. 系统 mock 生成火车往返票价。
|
||||
4. 系统调用现有差旅测算拿到住宿与补助小计。
|
||||
5. 系统在申请预览中展示系统预估费用,并允许进入确认提交。
|
||||
|
||||
## 功能能力
|
||||
|
||||
输入:
|
||||
|
||||
- 地点:用于差旅规则匹配和交通 mock 城市层级判断。
|
||||
- 天数:用于住宿与补助小计。
|
||||
- 出行方式:火车、飞机、轮船。
|
||||
- 职级:沿用现有差旅测算接口的标准匹配入参。
|
||||
|
||||
输出:
|
||||
|
||||
- 交通费用口径:说明当前是 mock 票价估算,报销阶段按真实票据复核。
|
||||
- 规则测算参考:展示交通、住宿、补助拆分与合计。
|
||||
- 系统预估费用:写入申请金额字段,用于后续申请提交。
|
||||
- 估算来源字段:记录 mock 交通估算和规则测算结果,便于后续审计解释。
|
||||
|
||||
边界:
|
||||
|
||||
- 缺少地点或天数时,仍不能完成住宿与补助测算,需要继续补齐基础字段。
|
||||
- 缺少出行方式时,仍需用户选择火车、飞机或轮船。
|
||||
- 后端纯对话流程没有前端规则测算结果时,使用保守的 mock 住宿/补助兜底。
|
||||
|
||||
## 方案设计
|
||||
|
||||
前端:
|
||||
|
||||
- 新增申请估算工具模块,集中维护交通 mock 票价、金额格式化和总额合成。
|
||||
- `expenseApplicationPreview` 在差旅规则测算返回后,将交通 mock 金额叠加到住宿与补助小计。
|
||||
- 将 `amount` 字段改为“系统预估费用”,并设为非手动必填字段。
|
||||
- 申请提交文本使用系统生成的金额。
|
||||
|
||||
后端:
|
||||
|
||||
- 新增申请系统预估服务模块,避免继续向已经超过 800 行的 `user_agent_application.py` 堆业务算法。
|
||||
- 后端对话在基础字段和出行方式齐全时自动补 `amount`、`transport_policy`、`policy_estimate`。
|
||||
- 缺失字段追问只保留基础字段和出行方式,不再追问预估金额。
|
||||
|
||||
数据与接口:
|
||||
|
||||
- 不新增数据库字段。
|
||||
- 不新增外部接口。
|
||||
- 申请详情仍通过现有 `risk_flags_json.application_detail` 保存展示字段。
|
||||
|
||||
## 算法与公式
|
||||
|
||||
交通费用采用稳定 mock:
|
||||
|
||||
$$
|
||||
transport\_amount = round\_10(base(mode, location) \times 2)
|
||||
$$
|
||||
|
||||
其中 `mode` 为火车、飞机、轮船,`location` 用于判断一线/远途/沿海等粗粒度场景,默认按往返估算。
|
||||
|
||||
总费用:
|
||||
|
||||
$$
|
||||
estimated\_total = transport\_amount + lodging\_amount + allowance\_amount
|
||||
$$
|
||||
|
||||
前端的 `lodging_amount` 和 `allowance_amount` 来自现有差旅规则测算结果;后端兜底时按 mock 标准生成。
|
||||
|
||||
## 测试方案
|
||||
|
||||
- 前端单测:验证交通 mock、规则测算合计、行标签和提交文本。
|
||||
- 后端单测:验证选择交通方式后不再追问金额,而是直接生成预览。
|
||||
- 编排流测试:验证申请会话从补充出行方式直接进入确认,再提交成功。
|
||||
- 回归测试:用户明确输入金额时仍能提交,并保留兼容链路。
|
||||
|
||||
## 指标与验收
|
||||
|
||||
- 用户选择出行方式后,系统不再提示“用户预估费用”缺失。
|
||||
- 申请预览中出现“系统预估费用”。
|
||||
- 规则测算参考包含交通、住宿、补助三项拆分。
|
||||
- 前端定向测试和后端申请流程测试通过。
|
||||
|
||||
## 风险与开放问题
|
||||
|
||||
- mock 票价不代表真实票价,只适合作为申请阶段预算参考。
|
||||
- 后端兜底住宿/补助不能完全替代规则中心,前端有规则测算结果时应优先使用规则中心。
|
||||
- 后续接入真实票务接口后,应替换交通 mock 模块,不改变申请预览和提交的数据契约。
|
||||
@@ -1,34 +0,0 @@
|
||||
# 申请交通费用自动预估开发 TODO
|
||||
|
||||
## 调研与契约
|
||||
|
||||
- [x] 确认申请预览当前金额字段仍为用户手填。证据:`expenseApplicationPreview.js` 中 `amount` 标签为“用户预估费用”。
|
||||
- [x] 确认住宿与补助已有规则测算入口。证据:前端申请预览调用 `calculateTravelReimbursement` 并读取 `hotel_amount`、`allowance_amount`。
|
||||
- [x] 确认后端对话仍追问金额。证据:`user_agent_application.py` 的缺失字段包含 `amount`。
|
||||
|
||||
## 算法
|
||||
|
||||
- [x] 新增前端申请估算工具模块,提供火车、飞机、轮船 mock 交通费。证据:`expenseApplicationEstimate.js`。
|
||||
- [x] 将前端规则测算结果合成为交通 + 住宿 + 补助总额。证据:`expenseApplicationPreview.js` 的规则测算合成逻辑和前端定向测试。
|
||||
- [x] 新增后端申请估算工具模块,提供无前端上下文时的兜底估算。证据:`application_system_estimate.py`。
|
||||
|
||||
## 前端
|
||||
|
||||
- [x] 将申请预览金额标签改为“系统预估费用”。证据:`expenseApplicationPreview.js` 字段定义。
|
||||
- [x] 将 `amount` 改为系统估算字段,不再作为用户必填项阻塞。证据:`amount` 字段 `required: false` 且 `editable: false`。
|
||||
- [x] 更新交通费用口径文案,明确是模拟票价估算。证据:`buildTransportPolicyText` 输出模拟票价口径。
|
||||
- [x] 在规则测算成功后写入系统预估总费用。证据:前端测试 `application preview merges rule center travel estimate into highlighted rows`。
|
||||
|
||||
## 后端
|
||||
|
||||
- [x] 选择出行方式后自动生成系统预估费用。证据:后端测试 `test_user_agent_application_builds_system_estimate_after_transport_choice`。
|
||||
- [x] 缺失字段追问不再包含 `amount`。证据:后端申请流程定向测试。
|
||||
- [x] 后端预览和提交摘要统一展示“系统预估费用”。证据:`user_agent_application.py` 摘要表字段。
|
||||
|
||||
## 测试与验证
|
||||
|
||||
- [x] 更新前端申请预览定向测试。证据:`expense-application-fast-preview.test.mjs`。
|
||||
- [x] 更新后端用户 Agent 申请流程测试。证据:`test_user_agent_service.py`。
|
||||
- [x] 更新编排流申请提交测试。证据:`test_orchestrator_review_flow.py`。
|
||||
- [x] 运行前端定向测试,记录结果。证据:`node --test web/tests/expense-application-fast-preview.test.mjs`,14 passed。
|
||||
- [x] 在 `x-financial-main` 容器内运行后端定向测试,记录结果。证据:申请相关 7 个 UserAgent 用例通过、2 个 Orchestrator 用例通过;整包定向存在无关查询动作测试失败。
|
||||
@@ -1,88 +0,0 @@
|
||||
# 申请单关联归档状态概念文档
|
||||
|
||||
## 功能一句话
|
||||
|
||||
申请单审批完成后先进入关联单据状态,只有关联的报销单完成付款归档后,申请单才同步归档。
|
||||
|
||||
## 背景与问题
|
||||
|
||||
当前费用申请单审批完成后,部分列表和进度展示会把申请单视为归档;但业务上申请单只是完成了事前审批,还需要等待后续报销单关联、报销审批、付款完成后,申请单生命周期才真正闭环。
|
||||
|
||||
这会导致用户看到报销单仍在处理、申请单却已归档,或者报销单已完成但申请单还停留在进行中的割裂状态。
|
||||
|
||||
## 目标
|
||||
|
||||
1. 申请单审批完成不直接进入归档中心。
|
||||
2. 申请单进度在归档前增加“关联单据状态”节点。
|
||||
3. 已有关联报销单但未付款完成时,该节点显示“关联中”。
|
||||
4. 没有关联报销单时,该节点显示“未关联”。
|
||||
5. 关联报销单付款完成后,申请单同步进入“申请归档”。
|
||||
|
||||
## 非目标
|
||||
|
||||
1. 不新增数据库表。
|
||||
2. 不改变报销单本身的审批、付款权限。
|
||||
3. 不改变申请单审批通过自动生成报销草稿的现有能力。
|
||||
|
||||
## 用户与场景
|
||||
|
||||
涉及角色:
|
||||
|
||||
- 申请人:查看申请单是否已经关联后续报销单。
|
||||
- 审批人:审批申请单后不再误以为该申请已经归档。
|
||||
- 财务人员:付款完成报销单时,同步闭环关联申请单。
|
||||
|
||||
关键场景:
|
||||
|
||||
1. 申请单审批通过,但未生成或未关联报销单:显示“关联单据状态 / 未关联”。
|
||||
2. 申请单审批通过,并已生成报销草稿或报销单仍在流程中:显示“关联单据状态 / 关联中”。
|
||||
3. 关联报销单已付款:报销单进入已付款,申请单进入“申请归档”。
|
||||
|
||||
## 方案设计
|
||||
|
||||
后端:
|
||||
|
||||
- 申请单 `approved + 审批完成` 不再被归档查询命中。
|
||||
- 申请单只有 `approved + 申请归档` 才属于归档。
|
||||
- 报销单付款完成时,从 `application_handoff` 或 `application_link` 风险事件中读取关联申请单。
|
||||
- 找到关联申请单后,追加同步归档事件,并将申请单阶段置为“申请归档”。
|
||||
|
||||
前端:
|
||||
|
||||
- 申请单进度增加“关联单据状态”和“已归档”节点。
|
||||
- 审批完成但未归档的申请单,当前节点停留在“关联单据状态”。
|
||||
- 根据申请单自身的 `generated_draft_claim_no` 或报销单侧关联事件显示“关联中 / 未关联”。
|
||||
- 只有“申请归档”阶段才展示归档完成。
|
||||
|
||||
## 算法与公式
|
||||
|
||||
当前功能不涉及显式数学公式。
|
||||
|
||||
关联状态判断:
|
||||
|
||||
```text
|
||||
has_linked_reimbursement = exists(application.generated_draft_claim_no)
|
||||
or exists(reimbursement.risk_flags.application_claim_id/no == application.id/no)
|
||||
|
||||
application_archived = application.status in {approved, completed}
|
||||
and application.approval_stage == "申请归档"
|
||||
```
|
||||
|
||||
## 测试方案
|
||||
|
||||
1. 后端状态测试:审批完成申请单不归档,申请归档才归档。
|
||||
2. 后端付款测试:关联报销单付款后,申请单同步进入“申请归档”。
|
||||
3. 前端进度测试:审批完成申请单显示“关联单据状态”和“已归档”。
|
||||
4. 前端归档判断测试:`审批完成` 申请单不算归档,`申请归档` 才算归档。
|
||||
|
||||
## 验收标准
|
||||
|
||||
1. 单据中心普通视图仍能看到审批完成但未归档的申请单。
|
||||
2. 归档中心不会提前出现仅审批完成的申请单。
|
||||
3. 申请单进度在审批完成后能看到“关联单据状态”。
|
||||
4. 报销单付款完成后,关联申请单同步显示为归档。
|
||||
|
||||
## 风险与开放问题
|
||||
|
||||
- 旧数据中可能存在已经把申请单审批完成当作归档的数据,本次按新业务规则修正展示与查询口径。
|
||||
- 如果历史申请单缺少关联报销事件,只能展示“未关联”,不做自动猜测。
|
||||
@@ -1,8 +0,0 @@
|
||||
# 申请单关联归档状态开发 TODO
|
||||
|
||||
- [x] 梳理申请单审批完成、报销单关联、报销单付款、归档查询的现有链路。[CONCEPT: 背景与问题] 证据:已确认 `expense_claim_status_registry.py`、`expense_claim_access_policy.py`、`expense_claim_approval_flow.py`、`useRequests.js` 的当前行为。
|
||||
- [x] 调整后端归档查询口径:申请单 `审批完成` 不再视为归档,仅 `申请归档` 才归档。[CONCEPT: 方案设计] 证据:`ExpenseClaimAccessPolicy.build_archived_claim_condition()` 仅将 `APPLICATION_ARCHIVE_STAGE` 视为申请归档。
|
||||
- [x] 调整报销单付款完成逻辑:根据关联事件同步推进申请单到 `申请归档`。[CONCEPT: 方案设计] 证据:`mark_claim_paid()` 调用 `_archive_linked_applications_after_reimbursement_paid()`,新增付款同步测试通过。
|
||||
- [x] 调整前端申请单进度:增加 `关联单据状态` 与 `已归档` 节点,并显示 `关联中/未关联`。[CONCEPT: 方案设计] 证据:`useRequests.js` 新增申请单进度节点和关联状态计算。
|
||||
- [x] 补充前后端回归测试,覆盖未关联、关联中、已归档三类申请单状态。[CONCEPT: 测试方案] 证据:`requestProgressSteps.test.mjs`、`document-center-archived-scope.test.mjs`、`expense-claim-archive.test.mjs`、`test_expense_claim_service.py` 已覆盖。
|
||||
- [x] 在容器或前端定向测试中完成验证,并记录命令结果。[CONCEPT: 验收标准] 证据:前端 Node 定向测试、容器内 py_compile、状态/路由/归档/付款同步 pytest、`npm.cmd --prefix web run build` 均通过。
|
||||
@@ -1,137 +0,0 @@
|
||||
# 移动端适配概念文档
|
||||
|
||||
## 功能一句话
|
||||
|
||||
让手机浏览器打开 X-Financial Web 时具备可导航、可对话、控件完整可点的移动端体验。
|
||||
|
||||
## 背景与问题
|
||||
|
||||
本轮目标是 Web 在手机浏览器中的适配,不是 `mobile/app` 原生应用。
|
||||
|
||||
当前 Web 已经有部分移动端样式,例如侧边栏抽屉、移动遮罩和报销助手工作台的弹层基础样式,但仍有两个直接影响手机使用的问题:
|
||||
|
||||
- 应用壳层已有 `mobileSidebarOpen` 状态和 `.mobile-hamburger-btn` 样式,却缺少真正可见的手机导航按钮。
|
||||
- 报销智能体使用 Element Plus `el-dialog` 打开全屏工作台,但手机宽度下仍保留弹窗式留边和双栏工作台逻辑,底部输入区把附件、日期、计算器、输入框和发送按钮挤在一行,容易展示不全。
|
||||
|
||||
## 目标与非目标
|
||||
|
||||
### 目标
|
||||
|
||||
- 手机浏览器下提供明确的 Web 导航入口。
|
||||
- 智能体对话在手机视口下以全屏工作台呈现,不保留弹窗留边。
|
||||
- 对话主区、洞察侧栏、底部输入区在手机上不互相挤压。
|
||||
- 附件、日期、差旅计算器和发送控件在窄屏下完整展示。
|
||||
- 侧栏洞察在手机上转为覆盖式面板,不占用主对话宽度。
|
||||
- 保持 X-Financial 企业 SaaS 风格:白底、细边框、低饱和、直角控件。
|
||||
|
||||
### 非目标
|
||||
|
||||
- 本轮不改 `mobile/app` 原生应用。
|
||||
- 本轮不重写所有 Web 业务页面为移动端卡片流。
|
||||
- 本轮不调整后端接口、数据库和智能体业务协议。
|
||||
- 本轮不改变报销助手的会话、附件、日期和差旅计算器业务逻辑。
|
||||
|
||||
## 用户与场景
|
||||
|
||||
- 员工、财务或审批人员在手机浏览器中临时打开 Web 工作台。
|
||||
- 用户通过侧边栏进入单据、预算、票据夹或智能体助手。
|
||||
- 用户在报销智能体中上传附件、选择业务日期、打开差旅计算器并发送问题。
|
||||
|
||||
## 功能能力
|
||||
|
||||
### 手机导航入口
|
||||
|
||||
输入:
|
||||
|
||||
- 手机浏览器打开 Web。
|
||||
- 视口宽度小于等于 `760px`。
|
||||
|
||||
输出:
|
||||
|
||||
- 页面右上角显示移动端导航按钮。
|
||||
- 点击按钮打开侧边栏抽屉。
|
||||
- 点击遮罩或导航项关闭侧边栏。
|
||||
|
||||
### 智能体全屏工作台
|
||||
|
||||
输入:
|
||||
|
||||
- 用户从工作台、单据、票据夹或预算中心打开报销智能体。
|
||||
- 当前视口为手机宽度。
|
||||
|
||||
输出:
|
||||
|
||||
- `el-dialog` 使用 100dvh 全屏,占满手机浏览器可视区域。
|
||||
- 工作台不再保留外边距和圆角弹窗感。
|
||||
- 对话主面板独占宽度。
|
||||
- 右侧洞察面板在打开时覆盖主对话,不挤压主对话宽度。
|
||||
|
||||
### 底部输入控件
|
||||
|
||||
输入:
|
||||
|
||||
- 用户添加附件、选择日期、打开差旅计算器或输入文本。
|
||||
|
||||
输出:
|
||||
|
||||
- 工具按钮在手机上独占一行,固定三列展示。
|
||||
- 输入框和发送按钮在下一行展示。
|
||||
- 日期与差旅计算器浮层改为固定底部浮层,宽度适配手机视口。
|
||||
- 附件区域可滚动,避免把输入区挤出屏幕。
|
||||
|
||||
## 方案设计
|
||||
|
||||
### Web 壳层
|
||||
|
||||
- 在 `AppShellRouteView.vue` 增加 `.mobile-hamburger-btn` 模板节点。
|
||||
- 复用现有 `mobileSidebarOpen` 状态和遮罩关闭逻辑。
|
||||
- 在 `app.css` 中补齐按钮默认隐藏,手机媒体查询内显示。
|
||||
|
||||
### 报销智能体
|
||||
|
||||
- 保持 `TravelReimbursementCreateView.vue` 的业务结构不变。
|
||||
- 在 `travel-reimbursement-create-view-part4.css` 的手机断点中覆盖 Element Plus 弹层、工作台、布局和输入区样式。
|
||||
- 手机断点下:
|
||||
- overlay padding 设为 `0`。
|
||||
- 工作台 `height/min-height` 使用 `100dvh`。
|
||||
- `assistant-layout` 改为单列。
|
||||
- `insight-panel-shell` 改为绝对定位覆盖面板。
|
||||
- `composer-row` 改为两行网格布局。
|
||||
|
||||
## 算法与公式
|
||||
|
||||
当前功能不涉及显式数学公式。
|
||||
|
||||
核心断点规则:
|
||||
|
||||
```css
|
||||
@media (max-width: 760px) {
|
||||
/* phone browser layout */
|
||||
}
|
||||
```
|
||||
|
||||
## 测试方案
|
||||
|
||||
- 静态回归测试:`node --test web/tests/app-shell-mobile-browser.test.mjs`。
|
||||
- Web 构建:`npm.cmd --prefix web run build`。
|
||||
- 手机视口浏览器验证:
|
||||
- 以 390x844 或相近视口打开 Web。
|
||||
- 验证导航按钮可见且侧栏可打开。
|
||||
- 打开报销智能体,验证工作台占满手机视口。
|
||||
- 验证底部附件、日期、差旅计算器、输入框和发送按钮完整展示。
|
||||
- 打开洞察面板,验证其覆盖展示而不是挤压主对话。
|
||||
|
||||
## 指标与验收
|
||||
|
||||
- `mobile/app` 无本轮改动。
|
||||
- 手机浏览器下 Web 存在可点击导航入口。
|
||||
- 报销智能体不再呈现带留边的弹窗效果。
|
||||
- 底部输入工具控件不被挤出屏幕。
|
||||
- 定向静态测试通过。
|
||||
- Web 构建通过。
|
||||
|
||||
## 风险与开放问题
|
||||
|
||||
- 其他业务页面仍可能存在表格密度过高的问题,需要按页面继续做字段折叠或卡片化。
|
||||
- 一些二级确认弹窗、票据预览和日期控件需要后续逐页检查。
|
||||
- 手机浏览器地址栏收起/展开会改变视口高度,后续应继续用真实设备补充验证。
|
||||
@@ -1,27 +0,0 @@
|
||||
# 移动端适配 TODO
|
||||
|
||||
## 调研与边界
|
||||
|
||||
- [x] 确认本轮范围是手机浏览器打开 Web,不是 `mobile/app` 原生应用。证据:`CONCEPT.md` 已明确目标与非目标。[CONCEPT: 目标与非目标]
|
||||
- [x] 梳理 Web 应用壳层移动端状态。证据:`AppShellRouteView.vue` 已有 `mobileSidebarOpen` 和遮罩,但缺少按钮节点。[CONCEPT: 手机导航入口]
|
||||
- [x] 梳理报销智能体弹层结构。证据:`TravelReimbursementCreateView.vue` 使用 `el-dialog`、`assistant-layout`、`composer-row` 和洞察侧栏。[CONCEPT: 背景与问题]
|
||||
|
||||
## Web 实现
|
||||
|
||||
- [x] 在 Web 壳层补充手机导航按钮。证据:`AppShellRouteView.vue` 新增 `.mobile-hamburger-btn`,点击打开 `mobileSidebarOpen`。[CONCEPT: Web 壳层]
|
||||
- [x] 补齐手机导航按钮默认隐藏与手机断点显示。证据:`app.css` 新增默认隐藏,`760px` 断点内显示按钮。[CONCEPT: Web 壳层]
|
||||
- [x] 将报销智能体手机视口改为真正全屏。证据:`travel-reimbursement-create-view-part4.css` 覆盖 overlay padding、dialog 圆角和工作台 `100dvh`。[CONCEPT: 智能体全屏工作台]
|
||||
- [x] 将手机端洞察侧栏改为覆盖式面板。证据:`insight-panel-shell` 在手机断点下使用绝对定位和 `translateX` 切换。[CONCEPT: 智能体全屏工作台]
|
||||
- [x] 重排手机端底部输入区。证据:`composer-row` 改为两行网格,工具按钮独占一行,输入框和发送按钮在下一行。[CONCEPT: 底部输入控件]
|
||||
- [x] 调整日期和差旅计算器浮层。证据:手机断点下浮层使用固定底部定位并限制最大高度。[CONCEPT: 底部输入控件]
|
||||
|
||||
## 测试与验证
|
||||
|
||||
- [x] 运行 `node --test web/tests/app-shell-mobile-browser.test.mjs`。证据:2 个测试通过。[CONCEPT: 测试方案]
|
||||
- [x] 运行 `npm.cmd --prefix web run build`。证据:构建通过,保留既有 VueUse 注释和 chunk 体积 warning。[CONCEPT: 测试方案]
|
||||
- [ ] 使用手机视口打开 Web,验证导航、智能体全屏、底部控件完整展示和洞察覆盖面板。[CONCEPT: 测试方案]
|
||||
|
||||
## 后续增强
|
||||
|
||||
- [ ] 继续盘点高频表格页面的手机浏览器阅读体验。[CONCEPT: 风险与开放问题]
|
||||
- [ ] 逐页检查二级确认弹窗、票据预览、日期选择和复杂筛选在手机浏览器里的表现。[CONCEPT: 风险与开放问题]
|
||||
@@ -1,115 +0,0 @@
|
||||
# 财务与风险看板卡片重组
|
||||
|
||||
## 功能一句话
|
||||
|
||||
将财务看板的预算执行率合并进预算指标卡片,并重组风险看板尾部卡片,让异常排行和风险占比成为主要分析信息。
|
||||
|
||||
## 背景与问题
|
||||
|
||||
当前分析看板存在两个体验问题:
|
||||
|
||||
- 财务看板底部同时有“预算指标”和“预算执行率(本月)”两个预算卡片,信息相近但占用两块空间。
|
||||
- 风险看板中“算法闭环效果”和“近期高风险观察”对当前看板判断价值较低;“来源分布”展示 `unknown` 时会让用户误以为数据异常,实际用户想看每类风险占比。
|
||||
|
||||
## 目标与非目标
|
||||
|
||||
目标:
|
||||
|
||||
- 将预算执行率仪表图整合进“预算指标”卡片,取消单独的预算执行率卡片,并把整合后的预算指标卡放在“高额单据”右侧空白位。
|
||||
- 风险看板把“来源分布”改为“风险占比”,展示风险信号或风险类型占比。
|
||||
- 风险看板移除“算法闭环效果”和“近期高风险观察”卡片。
|
||||
- 异常排行重新设计为占满整张卡片的图表化内容,减少碎片列表感。
|
||||
|
||||
非目标:
|
||||
|
||||
- 不改后端接口,不新增风险或预算接口。
|
||||
- 不改顶部 KPI 和风险趋势图数据口径。
|
||||
- 不引入新的图表库,继续复用现有 `DonutChart`、`BarChart` 和 `GaugeChart`。
|
||||
|
||||
## 用户与场景
|
||||
|
||||
用户:
|
||||
|
||||
- 财务分析人员、风险复核人员、管理员。
|
||||
|
||||
场景:
|
||||
|
||||
- 财务人员查看预算指标时,一眼看到预算执行率、预算总额、已用和剩余额度。
|
||||
- 风险人员查看风险看板时,优先看到风险类型占比和异常维度排行,而不是来源未知或低价值尾部卡片。
|
||||
|
||||
## 功能能力
|
||||
|
||||
财务看板:
|
||||
|
||||
- “预算指标”卡片包含预算执行率仪表图和预算指标列表,桌面端与“高额单据”同处底部半宽行,避免预算信息独占新行造成留白。
|
||||
- `budgetSummary` 仍作为仪表图数据源。
|
||||
- `budgetMetrics` 仍作为指标列表数据源。
|
||||
- 单独 `budget-panel` 不再渲染。
|
||||
|
||||
风险看板:
|
||||
|
||||
- “来源分布”改为“风险占比”,数据来自 `signalDistribution` 或 `topRiskSignals`。
|
||||
- 异常排行卡片横跨整行,主图表填满卡片,下面只保留紧凑排行明细。
|
||||
- 删除算法闭环效果和近期高风险观察两个卡片。
|
||||
|
||||
## 方案设计
|
||||
|
||||
前端:
|
||||
|
||||
- `OverviewView.vue`
|
||||
- 删除独立预算执行率卡片。
|
||||
- 在预算指标卡片内部增加 `GaugeChart` 区域,与指标列表左右布局。
|
||||
|
||||
- `overview-view.css`
|
||||
- 调整 `budget-metrics-panel` 的布局宽度和内部栅格,桌面端占 6 栅格贴合“高额单据”右侧。
|
||||
- 新增预算整合布局样式,移动端自动单列。
|
||||
|
||||
- `useOverviewView.js`
|
||||
- 将 `riskSourceLegend` 改为风险占比 legend,优先使用风险信号分布。
|
||||
|
||||
- `RiskObservationDashboard.vue`
|
||||
- 风险占比卡片标题改为“风险占比”。
|
||||
- 异常排行卡片改为整行大卡。
|
||||
- 移除算法闭环效果和近期高风险观察模板与样式。
|
||||
|
||||
## 算法与公式
|
||||
|
||||
本次不改变后端算法,只改变前端展示。
|
||||
|
||||
风险占比:
|
||||
|
||||
$$
|
||||
share_i = \frac{count_i}{\sum_{j=1}^{n} count_j}
|
||||
$$
|
||||
|
||||
预算执行率沿用已有 `budgetSummary.ratio`:
|
||||
|
||||
$$
|
||||
budgetUsageRate = \frac{usedBudget}{totalBudget}
|
||||
$$
|
||||
|
||||
## 测试方案
|
||||
|
||||
- 前端源码测试:
|
||||
- 财务看板不再渲染独立 `budget-panel`。
|
||||
- 预算指标卡片包含 `GaugeChart`。
|
||||
- 风险看板标题为“风险占比”,不再使用“来源分布”。
|
||||
- 风险看板不再渲染算法闭环效果和近期高风险观察。
|
||||
- 异常排行卡片使用整行样式和图表填充样式。
|
||||
- 构建验证:
|
||||
- `node web/tests/risk-observation-dashboard.test.mjs`
|
||||
- 如有财务看板测试则补充运行。
|
||||
- `npm.cmd --prefix web run build`
|
||||
|
||||
## 指标与验收
|
||||
|
||||
- 财务看板底部不再多出单独“预算执行率(本月)”卡片。
|
||||
- 预算指标卡片内部能看到预算执行率和预算指标,并在桌面端填充“高额单据”右侧空白位。
|
||||
- 风险看板不再显示“算法闭环效果”和“近期高风险观察”。
|
||||
- 风险占比不再显示来源未知,而是展示具体风险占比。
|
||||
- 异常排行卡片占满整行,图表区域明显成为主内容。
|
||||
|
||||
## 风险与开放问题
|
||||
|
||||
- 当前工作区已有未提交改动,提交时必须只纳入本次相关文件。
|
||||
- 本次只改前端展示,如果后端风险信号为空,则仍需要显示“暂无数据”兜底。
|
||||
@@ -1,30 +0,0 @@
|
||||
# 财务与风险看板卡片重组 TODO
|
||||
|
||||
## 调研
|
||||
|
||||
- [x] 盘点财务预算卡片和风险看板卡片现状。[CONCEPT: 背景与问题] 证据:已检查 `OverviewView.vue`、`overview-view.css`、`RiskObservationDashboard.vue`、`useOverviewView.js` 和风险看板测试。
|
||||
|
||||
## 契约
|
||||
|
||||
- [x] 确认本次不改后端接口,只调整前端展示和数据映射。[CONCEPT: 目标与非目标] 证据:现有 `budgetSummary`、`budgetMetrics`、`signalDistribution` 和 `topRiskSignals` 足够支撑改动。
|
||||
|
||||
## 前端
|
||||
|
||||
- [x] 将预算执行率整合到预算指标卡片,移除独立预算执行率卡片。[CONCEPT: 财务看板] 证据:`OverviewView.vue` 中预算指标卡片内新增 `GaugeChart`,并保留在“高额单据”右侧的底部栅格位置;独立 `budget-panel` 已移除。
|
||||
- [x] 将风险“来源分布”改成“风险占比”,使用风险信号分布数据。[CONCEPT: 风险看板] 证据:`riskCompositionLegend` 优先读取 `signalDistribution`,标题显示“风险占比”。
|
||||
- [x] 移除风险看板“算法闭环效果”和“近期高风险观察”卡片。[CONCEPT: 风险看板] 证据:模板、计算属性和样式中的 `risk-effect-*`、`risk-recent-*` 已删除。
|
||||
- [x] 重设异常排行卡片为整行大图表布局。[CONCEPT: 风险看板] 证据:`.risk-ranking-panel` 改为 `grid-column: span 12`,并新增 `risk-ranking-chart-block`。
|
||||
|
||||
## 测试
|
||||
|
||||
- [x] 更新风险看板源码测试。[CONCEPT: 测试方案] 证据:`risk-observation-dashboard.test.mjs` 覆盖删卡、异常排行图表化、风险映射中文化和顶部时间范围驱动。
|
||||
- [x] 补充或更新财务看板源码测试。[CONCEPT: 测试方案] 证据:新增 `finance-dashboard-budget-card.test.mjs`,校验预算指标卡位于高额单据之后且桌面端 `grid-column: span 6`。
|
||||
- [x] 运行定向前端测试。[CONCEPT: 测试方案] 证据:`node web/tests/risk-observation-dashboard.test.mjs`、`node web/tests/finance-dashboard-ranking.test.mjs`、`node web/tests/finance-dashboard-budget-card.test.mjs` 通过。
|
||||
- [x] 运行前端构建验证。[CONCEPT: 测试方案] 证据:`npm.cmd --prefix web run build` 通过,仅保留 Vite 大 chunk 与第三方 PURE 注释警告。
|
||||
|
||||
## 验收
|
||||
|
||||
- [x] 确认财务看板只有一个预算卡片且含预算执行率。[CONCEPT: 指标与验收] 证据:源码测试确认 `budget-metrics-panel` 包含 `GaugeChart`、没有旧 `budget-panel`,并在桌面端填充“高额单据”右侧空白位。
|
||||
- [x] 确认风险占比展示具体风险类型,不再展示来源未知。[CONCEPT: 指标与验收] 证据:源码测试确认使用 `riskCompositionLegend` 和 `signalDistribution`,并补充 `budget_pressure`、`missing_material`、`simulation` 中文映射。
|
||||
- [x] 确认风险看板尾部仅保留重设计后的异常排行核心信息。[CONCEPT: 指标与验收] 证据:源码测试确认 `risk-ranking-visual`、`rankingChartItems` 生效,且 `risk-effect-panel`、`risk-recent-panel` 不再渲染。
|
||||
- [x] 提交并推送本次改动,避免纳入无关脏工作区文件。[CONCEPT: 风险与开放问题] 证据:本次看板相关文件将随 `feat(dashboard): reorganize budget and risk cards` 提交并推送到当前分支。
|
||||
@@ -1,167 +0,0 @@
|
||||
# 财务看板口径重构与画像模拟概念文档
|
||||
|
||||
## 功能一句话
|
||||
|
||||
把财务看板从“审批过程展示”调整为“财务费用经营分析”,并让半年模拟数据自然形成部门、预算、风险和员工画像。
|
||||
|
||||
## 背景与问题
|
||||
|
||||
当前财务看板存在三类偏差:
|
||||
|
||||
- 费用结构里直接展示 `travel_application` 等技术枚举,业务用户无法理解,且申请类口径不应混入报销费用结构。
|
||||
- 风险异常分布缺少完整中文映射,`missing_material`、`budget_pressure` 等风险信号以英文或半翻译方式泄露到页面。
|
||||
- 趋势图和底部卡片仍围绕审批量、审批时长展开,不符合财务看板的核心诉求。
|
||||
|
||||
半年模拟数据也需要服务于看板分析,不能只堆单据。它必须能支撑多部门费用排行、预算消耗、风险分布和员工画像。
|
||||
|
||||
## 目标
|
||||
|
||||
- 费用结构只展示费用科目中文名称,申请类技术值不裸露。
|
||||
- 风险异常分布统一中文化,并覆盖预算压力、材料缺失、预算超支等常见信号。
|
||||
- 趋势图改为每日报销数量和每日报销金额。
|
||||
- “审批瓶颈”改为财务关注项,展示预算、待付款、材料待补、风险金额等财务指标。
|
||||
- 部门排行按费用金额统计,而不是只看待处理审批金额。
|
||||
- 模拟数据在写入后可生成员工行为画像快照,画像与报销单据、预算压力和风险观察一致。
|
||||
|
||||
## 非目标
|
||||
|
||||
- 不重做财务看板整体视觉框架。
|
||||
- 不新增一套独立画像算法。
|
||||
- 不修改生产环境数据;所有批量修复只作用于 `SIM2026`、`SIM-EXP-2026`、`SIM-BUD-2026` 等模拟前缀数据。
|
||||
|
||||
## 用户与场景
|
||||
|
||||
- 财务经理:查看半年费用趋势、部门费用结构、预算执行和风险异常。
|
||||
- 部门负责人:理解本部门费用消耗和预算压力。
|
||||
- 审批人:查看员工画像时,能看到基于半年模拟数据形成的费用和流程质量画像。
|
||||
- 系统演示人员:用 100 人规模的模拟数据演示端到端效果。
|
||||
|
||||
## 功能能力
|
||||
|
||||
### 费用结构
|
||||
|
||||
输入为当前时间范围内有效报销单。
|
||||
|
||||
输出为费用科目金额占比:
|
||||
|
||||
- 排除草稿、退回、驳回、删除等非有效支出状态。
|
||||
- `travel_application` 等申请类值不直接展示;若历史数据仍存在,则归一为“差旅费”或从费用结构中排除申请类虚拟项。
|
||||
- 所有展示名称必须是中文。
|
||||
|
||||
### 风险异常分布
|
||||
|
||||
输入为风险观察和报销单风险标记。
|
||||
|
||||
输出为中文风险类型分布:
|
||||
|
||||
- `missing_material`:材料不完整
|
||||
- `budget_pressure`:预算压力偏高
|
||||
- `budget_overrun`:预算超支
|
||||
- `duplicate_invoice`:重复发票
|
||||
- `split_billing`:拆分报销
|
||||
- `amount_outlier`:金额异常
|
||||
|
||||
未知枚举用“风险观察”兜底,不能把英文下划线文案直接展示给用户。
|
||||
|
||||
### 每日报销趋势
|
||||
|
||||
趋势图按天返回:
|
||||
|
||||
- `claimCount`:每日有效报销单数量
|
||||
- `claimAmount`:每日有效报销金额
|
||||
|
||||
前端使用柱线组合图展示,左轴为单量,右轴为金额。
|
||||
|
||||
### 财务关注项
|
||||
|
||||
替代原“审批瓶颈”:
|
||||
|
||||
- 预算超支:超支预算池数量和金额。
|
||||
- 预算预警:预算使用率接近上限的池数量。
|
||||
- 材料待补:材料不完整风险数量。
|
||||
- 风险金额:当前范围内风险单据金额。
|
||||
- 待付款:已审批待付款金额。
|
||||
|
||||
### 员工画像
|
||||
|
||||
模拟数据写入后触发现有 `EmployeeBehaviorProfileService`:
|
||||
|
||||
- 生成 30、90、180 天画像快照。
|
||||
- 画像类型沿用费用支出、流程质量、AI 使用和审批行为。
|
||||
- 不伪造画像结果,只用模拟报销单、审批记录和风险数据驱动算法。
|
||||
|
||||
## 方案设计
|
||||
|
||||
### 后端
|
||||
|
||||
- 在 `FinanceDashboardService` 中新增费用类型与风险信号归一化方法。
|
||||
- 将 `_trend` 改为统计每日有效报销数量和金额,同时保留旧字段兼容前端灰度。
|
||||
- 将 `_department_ranking` 改为按有效费用金额统计。
|
||||
- 将 `_bottlenecks` 的返回语义改为财务关注项,字段名暂保留,降低接口破坏面。
|
||||
- 模拟数据脚本增加画像刷新入口,调用现有画像服务生成快照。
|
||||
|
||||
### 前端
|
||||
|
||||
- `TrendChart` 文案改为“报销单量”和“报销金额”。
|
||||
- `OverviewView` 标题改为:
|
||||
- 报销数量与金额趋势
|
||||
- 部门报销排行(费用金额)
|
||||
- 财务关注项
|
||||
- 底部列表继续复用现有紧凑卡片样式,不引入新视觉体系。
|
||||
|
||||
### 数据
|
||||
|
||||
- 部门分布按业务权重分配,避免只有市场部或技术部。
|
||||
- 近 10 日和本月窗口保证各核心部门都有可见费用。
|
||||
- 风险样本覆盖材料缺失、预算压力、重复发票、金额异常等类型。
|
||||
- 预算台账与报销单金额一致,能体现预警和超支。
|
||||
|
||||
## 算法与公式
|
||||
|
||||
费用金额:
|
||||
|
||||
$$
|
||||
amount_d = \sum_{c \in C_d} claimAmount(c)
|
||||
$$
|
||||
|
||||
其中 \(C_d\) 为某日有效状态报销单集合。
|
||||
|
||||
部门费用排行:
|
||||
|
||||
$$
|
||||
deptSpend_i = \sum_{c \in C_i} claimAmount(c)
|
||||
$$
|
||||
|
||||
预算使用率:
|
||||
|
||||
$$
|
||||
usageRate = \frac{reservedAmount + consumedAmount}{totalAmount} \times 100\%
|
||||
$$
|
||||
|
||||
风险金额:
|
||||
|
||||
$$
|
||||
riskAmount = \sum_{c \in C, hasRisk(c)=true} claimAmount(c)
|
||||
$$
|
||||
|
||||
## 测试方案
|
||||
|
||||
- 后端单元测试:验证费用类型中文化、风险信号中文化、趋势字段、部门排行和财务关注项。
|
||||
- 容器接口测试:在 `x-financial-main:/app` 调用 `/api/v1/analytics/finance-dashboard`。
|
||||
- 前端构建:使用项目现有 `npm.cmd` 构建路径。
|
||||
- 数据脚本 dry-run:确认模拟修复仅作用于 `SIM` 前缀数据。
|
||||
- 画像验证:确认 `employee_behavior_profile_snapshots` 生成模拟员工的快照。
|
||||
|
||||
## 指标与验收
|
||||
|
||||
- 财务看板接口不再返回 `travel_application`、`missing material`、`budget pressure` 等裸英文展示名。
|
||||
- 趋势字段包含 `claimCount` 和 `claimAmount`,前端标题不再出现“审批趋势”。
|
||||
- 部门排行至少覆盖 6 个核心部门的有效费用金额。
|
||||
- 财务关注项不再显示审批节点或平均处理时长。
|
||||
- 半年模拟数据可生成 100 人规模下的员工画像快照。
|
||||
|
||||
## 风险与开放问题
|
||||
|
||||
- 历史非模拟数据可能仍有 `待补充` 部门,当前方案只保证模拟数据合理,不强行修复历史数据。
|
||||
- 批量修复模拟数据涉及数据库更新和重建模拟预算台账,执行 `--apply` 前需要用户明确确认。
|
||||
- 前端浏览器验证若环境不稳定,可降级为接口 JSON、构建和容器内测试证据。
|
||||
@@ -1,99 +0,0 @@
|
||||
# 数据库状态字段审查
|
||||
|
||||
## 审查范围
|
||||
|
||||
- 容器:`x-financial-main`
|
||||
- 数据库:当前运行时 PostgreSQL
|
||||
- 字段范围:所有 `status`、`stage`、`approval`、`state` 相关列
|
||||
- 审查方式:只读查询 `information_schema` 与各表状态值分布
|
||||
|
||||
## 总体结论
|
||||
|
||||
- 当前数据库没有 `status_code`、`state_code`、`stage_code` 这类数字状态码字段。
|
||||
- 所有匹配到的状态字段类型都是 `character varying`。
|
||||
- 非业务运行态表,例如 agent 运行、工具调用、预算池、风险观察,主要使用英文机器码。
|
||||
- 报销主表 `expense_claims` 是当前最需要修复的表:`status` 使用英文码,`approval_stage` 同时混入英文码和中文节点名。
|
||||
|
||||
## 报销主表现状
|
||||
|
||||
`expense_claims` 当前共 498 条。
|
||||
|
||||
按单据类型拆分:
|
||||
|
||||
- 申请类单据:2 条,阶段为 `审批完成`、`直属领导审批`。
|
||||
- 普通报销单:1 条,阶段为 `待提交`。
|
||||
- 半年模拟报销单:495 条,主要问题都集中在这里。
|
||||
|
||||
`expense_claims.status` 当前值:
|
||||
|
||||
- `paid`:212
|
||||
- `approved`:98
|
||||
- `pending_payment`:67
|
||||
- `finance_review`:43
|
||||
- `submitted`:41
|
||||
- `returned`:17
|
||||
- `rejected`:13
|
||||
- `draft`:7
|
||||
|
||||
`expense_claims.approval_stage` 当前值:
|
||||
|
||||
- `payment`:279
|
||||
- `completed`:97
|
||||
- `finance_review`:43
|
||||
- `manager_review`:40
|
||||
- `supplement`:17
|
||||
- `rejected`:13
|
||||
- `draft`:6
|
||||
- `审批完成`:1
|
||||
- `待提交`:1
|
||||
- `直属领导审批`:1
|
||||
|
||||
## 问题判断
|
||||
|
||||
现在不是单纯中文显示问题,而是字段职责混乱:
|
||||
|
||||
- `status` 被当作流程机器状态使用。
|
||||
- `approval_stage` 既被当作流程节点,也被历史模拟数据写成英文状态码。
|
||||
- 单据中心和审批权限逻辑依赖 `submitted + 中文审批阶段`。
|
||||
- 旧模拟数据中的 `finance_review/manager_review/payment/completed` 会导致审核、归档、报销单分类偏差。
|
||||
|
||||
## 建议契约
|
||||
|
||||
短期先采用当前代码最接近的契约:
|
||||
|
||||
- `status`:稳定机器码,继续使用英文枚举。
|
||||
- `approval_stage`:当前流程节点,统一使用中文节点名。
|
||||
- 前端和接口展示层:只展示中文标签,不直接暴露机器码。
|
||||
|
||||
中期如要数字状态码,需要单独迁移:
|
||||
|
||||
- 增加 `status_code`、`approval_stage_code` 或独立状态字典表。
|
||||
- 保留现有字符串字段作为兼容层,避免一次性改动所有查询、权限、看板和智能体逻辑。
|
||||
- 完成迁移后再逐步让业务代码改读数字码。
|
||||
|
||||
## 报销主表修复映射
|
||||
|
||||
建议先只修 `expense_claims` 的模拟数据和历史异常阶段:
|
||||
|
||||
- `status=finance_review` → `status=submitted`,`approval_stage=财务审批`
|
||||
- `approval_stage=manager_review` → `直属领导审批`
|
||||
- `approval_stage=budget_review` → `预算管理者审批`
|
||||
- `approval_stage=finance_review` → `财务审批`
|
||||
- `status=pending_payment` → `approval_stage=待付款`
|
||||
- `status=paid` → `approval_stage=已付款`
|
||||
- `status=approved` 且为报销单 → `approval_stage=归档入账`
|
||||
- `status=approved` 且为申请单 → `approval_stage=审批完成`
|
||||
- `status=returned` → `approval_stage=待补充`
|
||||
- `status=rejected` → `approval_stage=已驳回`
|
||||
- `status=draft` → `approval_stage=待提交`
|
||||
|
||||
## 后续动作
|
||||
|
||||
- 已完成:只读审查数据库状态字段。
|
||||
- 已完成:模拟数据修复脚本支持 dry-run 和中文阶段归一化。
|
||||
- 已完成:新增报销状态注册表,统一状态码、标签、阶段别名与历史值归一化。
|
||||
- 已完成:新增只读审计脚本 `audit_expense_claim_statuses.py`,用于修复前后核对状态一致性。
|
||||
- 已验证:当前 498 张单据中 495 张模拟报销单需要归一化,集中在 `payment`、`completed`、`finance_review`、`manager_review` 等历史阶段值。
|
||||
- 待确认:执行模拟数据修复脚本 `--apply --refresh-profiles`。
|
||||
- 待确认:执行 mock 附件脚本 `--apply`。
|
||||
- 待开发:如确认要数字状态码,新增状态字典/状态码迁移方案。
|
||||
@@ -1,47 +0,0 @@
|
||||
# 财务看板口径重构与画像模拟开发 TODO
|
||||
|
||||
## 调研
|
||||
|
||||
- [x] 核对财务看板接口字段和页面消费位置。[CONCEPT: 背景与问题] 证据:`FinanceDashboardService`、`TrendChart`、`OverviewView` 已确认。
|
||||
- [x] 核对员工画像现有服务是否可复用。[CONCEPT: 员工画像] 证据:`EmployeeBehaviorProfileService` 已支持批量扫描和按员工刷新。
|
||||
|
||||
## 契约
|
||||
|
||||
- [x] 将趋势字段调整为 `claimCount`、`claimAmount`,并保留旧字段兼容。[CONCEPT: 每日报销趋势] 证据:`FinanceDashboardService._trend` 已返回新字段,定向测试通过。
|
||||
- [x] 将底部 `bottlenecks` 展示替换为预算指标。[CONCEPT: 财务关注项] 证据:页面展示预算池数量、总预算、已用预算、预占预算、可用预算、预警预算池。
|
||||
- [x] 补齐费用类型和风险类型中文归一化规则。[CONCEPT: 费用结构] 证据:接口 JSON 不再包含 `travel_application`、`missing_material`、`budget_pressure`。
|
||||
- [x] 建立报销状态注册表,集中管理状态码、中文标签、阶段别名和历史值归一化。[CONCEPT: 数据] 证据:`expense_claim_status_registry.py` 已新增。
|
||||
- [x] 将财务看板主指标改为财务口径,移除风险异常展示。[CONCEPT: 指标与验收] 证据:KPI 改为本期报销金额、报销单数、待付款金额、单均金额、预算使用率、付款完成率。
|
||||
|
||||
## 后端
|
||||
|
||||
- [x] 修改 `FinanceDashboardService` 的费用结构、趋势、部门排行、个人排行、高额单据和预算指标计算。[CONCEPT: 方案设计] 证据:`server/src/app/services/finance_dashboard.py` 已更新。
|
||||
- [x] 补充后端定向测试,覆盖英文枚举不外露和趋势字段。[CONCEPT: 测试方案] 证据:`test_finance_dashboard_uses_financial_terms_instead_of_approval_terms` 已新增。
|
||||
|
||||
## 前端
|
||||
|
||||
- [x] 修改 `TrendChart` 为报销单量和报销金额图。[CONCEPT: 前端] 证据:`TrendChart.vue` 已改为双轴单量/金额。
|
||||
- [x] 修改财务看板标题和底部列表文案。[CONCEPT: 前端] 证据:`OverviewView.vue` 标题已更新。
|
||||
- [x] 确认页面不再出现审批趋势、审批瓶颈文案。[CONCEPT: 指标与验收] 证据:`rg` 检查财务看板相关文案已清理。
|
||||
- [x] 将趋势拆为“每日报销金额”和“每日报销数量”两个单指标图。[CONCEPT: 每日报销趋势] 证据:`OverviewView.vue` 和 `TrendChart.vue` 已更新。
|
||||
- [x] 新增个人报销排行和本月高额单据列表。[CONCEPT: 指标与验收] 证据:财务看板模板已新增 `个人报销排行(本月)`、`本月高额单据`。
|
||||
- [x] 移除财务页“财务关注项”卡片,新增预算指标网格。[CONCEPT: 指标与验收] 证据:财务页模板已展示 `预算指标`,不再展示 `财务关注项`。
|
||||
|
||||
## 数据与画像
|
||||
|
||||
- [x] 修复半年模拟数据部门分布脚本,保持 dry-run 可审计。[CONCEPT: 数据] 证据:`repair_half_year_expense_demo_distribution.py` dry-run 返回六部门重分布计划。
|
||||
- [x] 为模拟数据写入脚本增加画像刷新入口。[CONCEPT: 员工画像] 证据:seed 与 repair 脚本均支持 `--refresh-profiles`。
|
||||
- [x] 将模拟数据修复脚本中的审批阶段规范为中文业务阶段。[CONCEPT: 数据] 证据:待审单统一为 `submitted + 财务审批/直属领导审批`,归档/付款阶段写入中文阶段。
|
||||
- [x] 增加报销状态只读审计脚本。[CONCEPT: 指标与验收] 证据:`audit_expense_claim_statuses.py` 可输出需要归一化的状态组合。
|
||||
- [x] 提高半年模拟数据单据密度。[CONCEPT: 数据] 证据:seed dry-run 计划在现有 495 单基础上新增 690 单,总量约 1185 单。
|
||||
- [ ] 在用户确认后执行模拟数据修复 `--apply`。[CONCEPT: 风险与开放问题]
|
||||
- [ ] 验证模拟员工画像快照已形成。[CONCEPT: 指标与验收]
|
||||
|
||||
## 验证
|
||||
|
||||
- [x] 在 `x-financial-main` 容器内运行后端定向测试,超时不超过 60s。[CONCEPT: 测试方案] 证据:`pytest -q server/tests/test_finance_dashboard_service.py server/tests/test_demo_company_simulation_seed.py`,4 passed。
|
||||
- [x] 运行前端构建或等价静态验证。[CONCEPT: 测试方案] 证据:`npm.cmd run build` 成功。
|
||||
- [x] 调用财务看板 API,确认 JSON 中不再泄露英文枚举并包含新指标。[CONCEPT: 指标与验收] 证据:容器内服务调用返回 `claimCount`、`claimAmount`,英文枚举检查为 false。
|
||||
- [x] 验证单据中心财务角色可以看到公司报销单与归档单。[CONCEPT: 测试方案] 证据:`test_list_claims_returns_company_reimbursements_for_finance_document_center` 与归档测试通过。
|
||||
- [x] 验证财务看板真实 payload 不含风险展示文案,部门排行不含“待补充”。[CONCEPT: 指标与验收] 证据:容器内服务调用 `contains_risk_text=false`、`contains_pending_fill_department=false`。
|
||||
- [x] 验证预算指标真实 payload。[CONCEPT: 指标与验收] 证据:容器内服务调用返回 6 个 `budget_metrics`,且 `contains_focus_label=false`。
|
||||
@@ -1,153 +0,0 @@
|
||||
# 财务看板排行口径与部门人员占比
|
||||
|
||||
## 功能一句话
|
||||
|
||||
在分析看板的财务看板中补齐部门人员报销占比,并让部门、个人、高额单据使用统一的排行时间筛选口径。
|
||||
|
||||
## 背景与问题
|
||||
|
||||
当前财务看板已有部门报销排行、个人报销排行和本月高额单据,但存在三个问题:
|
||||
|
||||
- 部门排行的时间筛选只有本周、本月、本季度,缺少本年和全部。
|
||||
- 个人报销排行标题固定为“本月”,实际无法由用户切换本月、本季度、本年和全部。
|
||||
- 高额单据旁缺少部门内人员报销构成,财务人员难以判断高额单据是否集中在少数人员或单一部门。
|
||||
|
||||
## 目标与非目标
|
||||
|
||||
目标:
|
||||
|
||||
- 新增“部门人员报销占比”饼图,放在“本月高额单据”左侧,并与排行时间筛选口径联动。
|
||||
- 部门报销排行增加参与人员数量,卡片空间完整展示排行内容。
|
||||
- 个人报销排行增加报销笔数和所属部门信息,卡片空间完整展示排行内容。
|
||||
- 部门排行、个人排行、高额单据、部门人员占比统一支持:本月、本季度、本年、全部。
|
||||
|
||||
非目标:
|
||||
|
||||
- 不新增独立页面。
|
||||
- 不重做顶部 KPI、趋势图、预算指标和系统/风险/数字员工看板。
|
||||
- 不引入新的图表库,继续复用现有 ECharts 封装组件。
|
||||
|
||||
## 用户与场景
|
||||
|
||||
用户:
|
||||
|
||||
- 高级财务人员、预算监控员、管理员。
|
||||
|
||||
场景:
|
||||
|
||||
- 财务人员进入分析看板后,查看不同时间口径下的部门费用集中度。
|
||||
- 财务人员切换本季度、本年或全部后,对比部门排行、个人排行、高额单据和人员占比。
|
||||
- 财务人员判断某部门报销金额高,是因为多人正常报销,还是少数人集中报销。
|
||||
|
||||
## 功能能力
|
||||
|
||||
输入:
|
||||
|
||||
- `department_range` 查询参数,取值:`本月`、`本季度`、`本年`、`全部`。
|
||||
|
||||
输出:
|
||||
|
||||
- `department_ranking`:部门报销排行,新增 `employeeCount`。
|
||||
- `employee_ranking`:个人报销排行,保留金额、笔数、部门,并随筛选口径变化。
|
||||
- `top_claims`:高额单据,随筛选口径变化,标题不再固定为本月。
|
||||
- `department_employee_mix`:部门人员报销占比饼图数据。
|
||||
|
||||
状态与边界:
|
||||
|
||||
- 没有真实数据时返回空数组或“暂无数据”占位。
|
||||
- 草稿、删除等非支出口径状态不参与金额排行。
|
||||
- 缺失部门或人员名称的数据不进入排行和占比图。
|
||||
- `全部` 表示所有可用报销单据,不按日期裁剪。
|
||||
|
||||
## 方案设计
|
||||
|
||||
后端:
|
||||
|
||||
- 在 `FinanceDashboardService` 中扩展排行时间范围解析。
|
||||
- 将 `department_range` 作为排行分析窗口,统一供部门排行、个人排行、高额单据和部门人员占比使用。
|
||||
- 部门排行按部门聚合金额、单据数、待付款金额和人员数量。
|
||||
- 部门人员占比按“部门 + 人员”聚合金额,展示排名靠前的人员构成,名称格式为 `部门 · 人员`。
|
||||
|
||||
接口:
|
||||
|
||||
- `GET /api/v1/analytics/finance-dashboard` 保持原路径。
|
||||
- `department_range` 支持 `本月`、`本季度`、`本年`、`全部`。
|
||||
- 响应体新增 `department_employee_mix`。
|
||||
|
||||
前端:
|
||||
|
||||
- `analytics.js` 增加 `departmentEmployeeMix` 归一化。
|
||||
- `metrics.js` 将 `departmentRangeOptions` 改为 `本月 / 本季度 / 本年 / 全部`。
|
||||
- `useOverviewView.js` 新增部门人员占比 legend,并让部门/个人排行读取新增字段。
|
||||
- `OverviewView.vue` 调整财务看板底部布局:
|
||||
- 部门排行占更宽区域,并保留筛选器。
|
||||
- 个人排行占更宽区域,并增加相同筛选器。
|
||||
- 高额单据卡片左侧放部门人员报销占比饼图,右侧放高额单据列表。
|
||||
- 样式继续沿用企业 SaaS 直角、低饱和、Element Plus 控件和已有 `DonutChart` / `BarChart`。
|
||||
|
||||
## 算法与公式
|
||||
|
||||
支出金额:
|
||||
|
||||
$$
|
||||
amount_i = claim_i.amount
|
||||
$$
|
||||
|
||||
部门金额:
|
||||
|
||||
$$
|
||||
departmentAmount_d = \sum_{i \in claims(d)} amount_i
|
||||
$$
|
||||
|
||||
部门人员数:
|
||||
|
||||
$$
|
||||
employeeCount_d = \left| distinct(employeeName_i), i \in claims(d) \right|
|
||||
$$
|
||||
|
||||
个人金额:
|
||||
|
||||
$$
|
||||
employeeAmount_e = \sum_{i \in claims(e)} amount_i
|
||||
$$
|
||||
|
||||
部门人员报销占比:
|
||||
|
||||
$$
|
||||
share_{d,e} = \frac{\sum_{i \in claims(d,e)} amount_i}{\sum_{i \in rankingClaims} amount_i}
|
||||
$$
|
||||
|
||||
其中 `rankingClaims` 为当前 `department_range` 时间口径下过滤后的有效报销单据。
|
||||
|
||||
## 测试方案
|
||||
|
||||
- 后端单元测试:
|
||||
- 覆盖 `department_range=本年` 和 `department_range=全部`。
|
||||
- 验证部门排行返回 `employeeCount`。
|
||||
- 验证个人排行随口径变化。
|
||||
- 验证 `department_employee_mix` 返回正确人员占比数据。
|
||||
- 前端源码测试:
|
||||
- 验证筛选选项包含本月、本季度、本年、全部。
|
||||
- 验证个人排行和部门排行都有筛选器。
|
||||
- 验证高额单据卡片包含部门人员报销占比图。
|
||||
- 验证服务层归一化新增字段。
|
||||
- 构建验证:
|
||||
- `npm.cmd --prefix web run build`。
|
||||
- 容器验证:
|
||||
- 后端测试在 `x-financial-main:/app` 中运行,超时不超过 60s。
|
||||
- 可用时通过接口检查 `department_employee_mix`、`employeeCount` 和 `department_range=全部`。
|
||||
|
||||
## 指标与验收
|
||||
|
||||
- 财务看板接口返回 `department_employee_mix`。
|
||||
- 部门排行每项返回 `employeeCount`。
|
||||
- 部门排行和个人排行都可选择本月、本季度、本年、全部。
|
||||
- 个人排行标题不再固定“本月”。
|
||||
- 高额单据卡片左侧显示部门人员报销占比饼图。
|
||||
- 定向后端测试和前端构建通过。
|
||||
|
||||
## 风险与开放问题
|
||||
|
||||
- 当前工作区存在大量未提交变更,提交时必须只纳入本次相关文件。
|
||||
- 如果浏览器自动化不可用,前端以源码测试、构建和接口验证为主要证据。
|
||||
- `全部` 口径数据量可能更大,当前实现继续沿用内存聚合;后续数据量过大时再考虑 SQL 聚合优化。
|
||||
@@ -1,35 +0,0 @@
|
||||
# 财务看板排行口径与部门人员占比 TODO
|
||||
|
||||
## 调研
|
||||
|
||||
- [x] 盘点财务看板后端聚合、前端服务、页面布局和测试现状。[CONCEPT: 背景与问题] 证据:已检查 `FinanceDashboardService`、`analytics.js`、`useOverviewView.js`、`OverviewView.vue`、`test_finance_dashboard_service.py`。
|
||||
|
||||
## 契约
|
||||
|
||||
- [x] 扩展 `department_range` 支持 `本月 / 本季度 / 本年 / 全部`。[CONCEPT: 功能能力] 证据:`FinanceDashboardService._resolve_ranking_scope` 和 `departmentRangeOptions` 已更新。
|
||||
- [x] 响应体新增 `department_employee_mix`,部门排行新增 `employeeCount`。[CONCEPT: 方案设计] 证据:`FinanceDashboardRead`、`_department_ranking`、`_department_employee_mix` 已更新。
|
||||
|
||||
## 后端
|
||||
|
||||
- [x] 修改财务看板服务的排行时间范围解析,统一驱动部门排行、个人排行、高额单据和人员占比。[CONCEPT: 方案设计] 证据:`ranking_claims` 同时供四类排行/图表使用。
|
||||
- [x] 新增部门人员报销占比聚合逻辑。[CONCEPT: 算法与公式] 证据:新增 `_department_employee_mix`,按部门和人员聚合金额并返回饼图数据。
|
||||
- [x] 更新快照缓存兼容新增字段。[CONCEPT: 接口] 证据:`SNAPSHOT_SCHEMA_VERSION = "finance-dashboard-ranking-v2"` 已加入快照缓存 key。
|
||||
|
||||
## 前端
|
||||
|
||||
- [x] 更新前端服务归一化和筛选选项。[CONCEPT: 前端] 证据:`analytics.js` 支持 `departmentEmployeeMix`,`metrics.js` 选项为本月/本季度/本年/全部。
|
||||
- [x] 调整财务看板底部布局,新增部门人员报销占比饼图。[CONCEPT: 前端] 证据:`OverviewView.vue` 的 `top-claim-split` 左侧接入 `DonutChart`。
|
||||
- [x] 部门排行和个人排行展示人员数、单据数等辅助信息,并占满卡片空间。[CONCEPT: 前端] 证据:`BarChart.vue` 支持 `meta`,排行卡片跨度改为 6 栅格。
|
||||
|
||||
## 测试
|
||||
|
||||
- [x] 补充后端定向测试,覆盖排行时间口径、人员数和部门人员占比。[CONCEPT: 测试方案] 证据:`test_finance_dashboard_ranking_range_supports_year_and_all_scope` 已新增。
|
||||
- [x] 补充前端源码测试,覆盖筛选器和新增图表字段。[CONCEPT: 测试方案] 证据:新增 `web/tests/finance-dashboard-ranking.test.mjs`。
|
||||
- [x] 在 `x-financial-main` 容器内运行后端定向测试,超时不超过 60s。[CONCEPT: 测试方案] 证据:`pytest -q server/tests/test_finance_dashboard_service.py`,4 passed。
|
||||
- [x] 运行前端定向测试或构建验证。[CONCEPT: 测试方案] 证据:`node web/tests/finance-dashboard-ranking.test.mjs`,3 passed;`npm.cmd --prefix web run build` 通过。
|
||||
|
||||
## 验收
|
||||
|
||||
- [x] 调用财务看板接口验证 `department_range=全部` 返回新增字段。[CONCEPT: 指标与验收] 证据:接口返回 `has_department_employee_mix=true`、`department_employee_mix_count=6`、部门排行含 `employeeCount=67`。
|
||||
- [x] 更新本 TODO 的完成证据。[CONCEPT: 指标与验收] 证据:本文件已补充每项完成证据。
|
||||
- [ ] 提交并推送本次功能改动,避免纳入无关脏工作区变更。[CONCEPT: 风险与开放问题] 阻塞:工作区已有大量未提交改动,且本次相关后端文件依赖未跟踪的财务看板快照/常量文件,直接提交会混入既有改动,单独提交又可能缺依赖。
|
||||
@@ -1,106 +0,0 @@
|
||||
# 费用审批动态路由概念文档
|
||||
|
||||
## 功能一句话
|
||||
|
||||
让费用申请和报账在直属领导审批后,按预算风险、规则风险和员工历史风险动态决定是否进入预算管理者复核。
|
||||
|
||||
## 背景与问题
|
||||
|
||||
当前申请单默认进入预算管理者审批,报账单默认进入财务审批,审批路径偏固定。业务上更合理的方式是:预算充足、当前无风险、历史画像正常的单据减少审批层级;存在超预算、规则命中、超标或历史风险异常的单据交给预算管理者做二次确认。
|
||||
|
||||
## 目标与非目标
|
||||
|
||||
目标:
|
||||
|
||||
- 申请环节:低风险且预算充足时,直属领导审批后直接完成申请并生成报销草稿。
|
||||
- 申请环节:超预算、预算预警、当前风险或历史风险异常时,进入预算管理者审批。
|
||||
- 报账环节:低风险且预算充足时,直属领导审批后进入财务审批。
|
||||
- 报账环节:超预算、超标、当前风险或历史风险异常时,先进入预算管理者审批,再进入财务审批。
|
||||
- 审批记录中保留路由决策依据,便于追溯。
|
||||
|
||||
非目标:
|
||||
|
||||
- 不改预算占用、释放、核销的资金动作语义。
|
||||
- 不引入新的审批流配置页面。
|
||||
- 不让大模型参与最终审批路由裁判。
|
||||
|
||||
## 用户与场景
|
||||
|
||||
- 普通员工:提交费用申请或报账。
|
||||
- 直属领导:确认业务必要性。
|
||||
- 预算管理者:只复核有预算或风险关注项的单据。
|
||||
- 财务人员:处理报账财务终审和付款前流程。
|
||||
|
||||
## 功能能力
|
||||
|
||||
路由决策输入:
|
||||
|
||||
- 单据基本信息:金额、费用类型、发生时间、部门、项目、申请人。
|
||||
- 预算测算:预算池匹配、可用余额、预算使用率、预警阈值、超预算金额。
|
||||
- 当前风险:预算标记、规则中心风险、提交预审风险、票据/附件风险、超标风险。
|
||||
- 历史风险:同一员工近一段时间内的实质风险记录。
|
||||
|
||||
路由决策输出:
|
||||
|
||||
- `requires_budget_review`:是否需要预算管理者复核。
|
||||
- `route`:下一环节建议。
|
||||
- `reasons`:触发预算复核或跳过的原因。
|
||||
- `budget_result`:预算模型摘要。
|
||||
- `current_risk_count`、`historical_risk_count`:当前和历史风险计数。
|
||||
|
||||
## 方案设计
|
||||
|
||||
后端新增独立审批路由决策模块,避免在审批主流程中堆条件。
|
||||
|
||||
直属领导审批时:
|
||||
|
||||
1. 调用预算服务计算当前单据预算影响。
|
||||
2. 读取当前单据风险标记,过滤审批记录等非风险事件。
|
||||
3. 查询同一员工近期历史单据,统计实质风险记录。
|
||||
4. 生成路由决策标记并写入 `risk_flags_json`。
|
||||
5. 根据结果决定下一环节:
|
||||
- 申请单:预算复核或审批完成。
|
||||
- 报账单:预算复核或财务审批。
|
||||
|
||||
预算管理者审批时:
|
||||
|
||||
- 申请单进入审批完成,并生成报销草稿。
|
||||
- 报账单进入财务审批。
|
||||
|
||||
## 算法与公式
|
||||
|
||||
路由决策不是单一分数,而是规则化闸口:
|
||||
|
||||
$$
|
||||
requires\_budget\_review =
|
||||
budget\_risk \lor current\_risk \lor historical\_risk
|
||||
$$
|
||||
|
||||
其中:
|
||||
|
||||
- `budget_risk = rating in {block, review, caution} or risk_level in {medium, high, critical}`
|
||||
- `current_risk = 当前单据存在 medium/high/critical 实质风险标记`
|
||||
- `historical_risk = 同一员工近期存在实质风险记录`
|
||||
|
||||
实质风险标记排除审批通过、退回、付款、路由说明等流程记录,只保留预算、规则、AI 预审、附件、政策超标等风险来源。
|
||||
|
||||
## 测试方案
|
||||
|
||||
- 单元测试:低风险申请跳过预算管理者并生成报销草稿。
|
||||
- 单元测试:高风险报账进入预算管理者审批,预算审批后进入财务审批。
|
||||
- 回归测试:原有风险规则生成、申请提交、阶段化风险规则执行继续通过。
|
||||
- 容器验证:在 `x-financial-main:/app/server` 内运行定向 pytest。
|
||||
|
||||
## 指标与验收
|
||||
|
||||
- 低风险申请不会固定进入预算管理者审批。
|
||||
- 风险报账会进入预算管理者审批。
|
||||
- 报账经过预算管理者审批后仍需进入财务终审。
|
||||
- 每次动态路由都有可追溯的 `approval_routing` 标记。
|
||||
- 预算资金动作仍由原有提交、退回、财务终审链路处理。
|
||||
|
||||
## 风险与开放问题
|
||||
|
||||
- 历史风险的口径会影响预算管理者工作量,当前一期采用“存在实质风险即复核”的严格口径。
|
||||
- 缺失预算池时是否全部进入预算复核,当前按预算风险处理。
|
||||
- 后续如要支持可配置路由阈值,应新增配置表或策略服务,而不是继续改审批流分支。
|
||||
@@ -1,9 +0,0 @@
|
||||
# 费用审批动态路由 TODO
|
||||
|
||||
- [x] 调研现有审批流、预算模型和风险标记结构。[CONCEPT: 方案设计] 证据:已梳理 `expense_claim_approval_flow.py`、`budget.py`、`budget_expense_control.py`、`expense_claim_risk_review.py`。
|
||||
- [x] 新增审批路由决策模块,统一输出是否需要预算复核。[CONCEPT: 功能能力] 证据:新增 `expense_claim_approval_routing.py`。
|
||||
- [x] 接入申请单直属领导审批后的动态路由。[CONCEPT: 方案设计] 证据:`ExpenseClaimApprovalFlowMixin.approve_claim` 根据路由结果完成或进入预算审批。
|
||||
- [x] 接入报账单直属领导审批后的动态路由,并允许报账经过预算管理者后进入财务审批。[CONCEPT: 方案设计] 证据:报账单预算审批后进入 `FINANCE_APPROVAL_STAGE`。
|
||||
- [x] 审批记录写入 `approval_routing` 决策标记。[CONCEPT: 指标与验收] 证据:审批通过时同时写入路由标记和 `route_decision` 摘要。
|
||||
- [x] 补充低风险申请跳过预算、高风险报账进入预算的测试。[CONCEPT: 测试方案] 证据:新增 `test_expense_claim_approval_routing.py`。
|
||||
- [x] 在 `x-financial-main:/app/server` 运行 60s 内定向验证。[CONCEPT: 测试方案] 证据:`uv run --with pytest python -m pytest ... -q`,6 passed。
|
||||
@@ -1,131 +0,0 @@
|
||||
# 费用申请审批财务规则概念文档
|
||||
|
||||
## 功能一句话
|
||||
|
||||
在财务规则中心新增《公司费用申请审批规则》,统一维护业务招待、办公用品和通用大额费用的事前申请与审批阈值,并让报销风险规则引用该规则执行。
|
||||
|
||||
## 背景与问题
|
||||
|
||||
现有系统已经有“业务招待无申请”“办公采购无申请”“大额费用无申请”等风险规则,但制度依据主要以风险规则 JSON 的口径字段存在,财务规则中心缺少一张可被制度管理员查看、编辑和追溯的规则表。
|
||||
|
||||
用户明确要求:
|
||||
|
||||
- 业务招待费超过 500 元需要申请。
|
||||
- 大额办公用品需要申请。
|
||||
- 金额超过 2000 元的费用都需要走审批。
|
||||
- 这些要求最好形成财务规则,而不是散落在代码或前端提示中。
|
||||
|
||||
## 目标与非目标
|
||||
|
||||
目标:
|
||||
|
||||
- 新增一张财务规则资产《公司费用申请审批规则》。
|
||||
- 规则资产以 Excel 形式进入 `finance-rules` 规则库,并在规则中心按“财务规则”展示。
|
||||
- 风险规则引用统一的 `finance_rule_code`,不再使用零散口径 code。
|
||||
- 报销阶段按结构化金额规则判断,而不是只靠关键词命中。
|
||||
- 关联有效申请单后不触发“缺少申请”风险。
|
||||
|
||||
非目标:
|
||||
|
||||
- 本轮不新增数据库字段。
|
||||
- 本轮不新增非本体业务字段。
|
||||
- 本轮不改造完整审批流节点,只补充申请前置与风险执行依据。
|
||||
|
||||
## 用户与场景
|
||||
|
||||
- 报销人:上传或录入业务招待、办公用品、大额费用报销时,系统自动识别是否缺少事前申请。
|
||||
- 直属领导和财务审核人:审核单据时能看到风险来自财务规则。
|
||||
- 财务制度管理员:能在规则中心看到并维护《公司费用申请审批规则》。
|
||||
|
||||
## 功能能力
|
||||
|
||||
### 财务规则表
|
||||
|
||||
规则表包含以下行:
|
||||
|
||||
- 业务招待费:单次费用金额大于 500 元时,必须先提交费用申请单。
|
||||
- 办公用品费:单次或批量采购金额大于 2000 元时,必须先提交办公采购或费用申请单。
|
||||
- 通用大额费用:任意费用金额大于 2000 元时,必须进入审批流程。
|
||||
|
||||
### 风险规则执行
|
||||
|
||||
- `meal` 与 `entertainment` 都视为业务招待费。
|
||||
- `office` 视为办公用品费。
|
||||
- `all` 视为通用大额费用。
|
||||
- 报销阶段没有关联有效申请单时,超过阈值命中高风险。
|
||||
- 已有关联申请单时,不命中缺少申请风险。
|
||||
|
||||
## 方案设计
|
||||
|
||||
### 后端
|
||||
|
||||
- 在 `agent_asset_spreadsheet.py` 中新增费用申请审批规则 code 与文件名常量。
|
||||
- 在财务规则同步中新增该资产的 metadata、Excel 工作簿生成和版本快照。
|
||||
- 在初始化和补齐逻辑中创建该财务规则资产,确保老库和新库都能看到。
|
||||
- 将三条风险规则改为 `composite_rule_v1`,使用金额阈值和申请单存在性执行。
|
||||
- 在 `risk_rule_template_executor.py` 中补齐 `application.*` 字段解析,桥接现有 `application_link` / `application_handoff` / `application_detail` 风险上下文。
|
||||
|
||||
### 前端
|
||||
|
||||
本轮不新增前端页面。规则中心已有财务规则和 JSON 风险规则展示能力,后端资产同步后前端可直接展示。
|
||||
|
||||
### 数据与本体
|
||||
|
||||
本轮只使用现有本体字段:
|
||||
|
||||
- `expense_type`
|
||||
- `amount`
|
||||
- `reason`
|
||||
- `application_claim_id`
|
||||
- `application_claim_no`
|
||||
- `application_detail`
|
||||
|
||||
不新增非本体字段。
|
||||
|
||||
## 算法与公式
|
||||
|
||||
业务招待费规则:
|
||||
|
||||
$$
|
||||
hit = expenseType \in \{meal, entertainment\} \land amount > 500 \land \neg hasApplication
|
||||
$$
|
||||
|
||||
办公用品规则:
|
||||
|
||||
$$
|
||||
hit = expenseType = office \land amount > 2000 \land \neg hasApplication
|
||||
$$
|
||||
|
||||
通用大额规则:
|
||||
|
||||
$$
|
||||
hit = amount > 2000 \land \neg hasApplication
|
||||
$$
|
||||
|
||||
其中:
|
||||
|
||||
- `amount` 来自 `claim.amount`。
|
||||
- `hasApplication` 来自 `application.id`、`application.claim_no` 或等价申请单上下文。
|
||||
|
||||
## 测试方案
|
||||
|
||||
- 单元测试:验证 `application.*` 字段能从已有申请关联上下文解析。
|
||||
- 规则执行测试:超过 500 元业务招待费且无申请命中风险。
|
||||
- 规则执行测试:超过 2000 元办公用品费且无申请命中风险。
|
||||
- 规则执行测试:超过 2000 元通用费用且无申请命中风险。
|
||||
- 规则执行测试:已关联申请单的超额费用不命中缺少申请风险。
|
||||
- 资产测试:规则中心种子数据包含《公司费用申请审批规则》,且 `config_json.tag` 为“财务规则”。
|
||||
|
||||
## 指标与验收
|
||||
|
||||
- 财务规则中心能看到新增规则资产。
|
||||
- 新增资产 `finance_rule_code` 统一为 `expense.preapproval.policy`。
|
||||
- 三条风险规则均引用该财务规则 code。
|
||||
- 容器内后端定向测试通过。
|
||||
- 不新增非本体业务字段。
|
||||
|
||||
## 风险与开放问题
|
||||
|
||||
- “大额办公用品”的金额阈值按用户同句“大额/超过 2000 都需要审批”落为 2000 元。
|
||||
- 当前申请单上下文主要存在 `risk_flags_json` 的申请关联 flag 中,本轮先补执行器解析,不新增外键字段。
|
||||
- 后续如果要支持不同部门或不同职级阈值,可以在同一张财务规则表中扩展分档行。
|
||||
@@ -1,23 +0,0 @@
|
||||
# 费用申请审批财务规则 TODO
|
||||
|
||||
## 调研与契约
|
||||
|
||||
- [x] 盘点现有财务规则资产、风险规则 JSON 与规则同步链路。[CONCEPT: 背景与问题] 证据:确认现有 `finance-rules` 仅差旅和通信两张核心规则表,前置申请规则当前在 `risk-rules` 中。
|
||||
- [x] 明确本轮不新增非本体业务字段。[CONCEPT: 数据与本体] 证据:规则只使用 `expense_type`、`amount`、`reason` 和申请单上下文。
|
||||
|
||||
## 后端实现
|
||||
|
||||
- [x] 新增《公司费用申请审批规则》财务规则资产常量与 Excel 工作簿内容。[CONCEPT: 财务规则表] 证据:`COMPANY_PREAPPROVAL_RULE_CODE`、`COMPANY_PREAPPROVAL_RULE_FILENAME` 和 `_ensure_company_preapproval_rule_spreadsheet_seed()` 已实现。
|
||||
- [x] 初始化种子和老库补齐逻辑都能创建该财务规则资产。[CONCEPT: 方案设计] 证据:`agent_foundation_asset_seed.py` 和 `agent_foundation_asset_topup.py` 均接入该资产。
|
||||
- [x] 将大额费用、业务招待、办公用品三条前置申请风险规则改为结构化金额判断。[CONCEPT: 风险规则执行] 证据:三条 `risk.application.*without_preapproval.json` 已改为 `composite_rule_v1`。
|
||||
- [x] 补齐 `application.*` 字段解析,支持从现有关联申请上下文判断是否已有申请。[CONCEPT: 后端] 证据:`risk_rule_template_executor.py` 新增 `_resolve_application_values()`。
|
||||
|
||||
## 测试与验证
|
||||
|
||||
- [x] 新增执行器测试:申请单上下文存在时 `application.id` 可解析。[CONCEPT: 测试方案] 证据:`test_application_context_values_are_available_to_composite_rules` 通过。
|
||||
- [x] 新增风险规则执行测试:业务招待费超过 500 元且无申请命中。[CONCEPT: 测试方案] 证据:`test_preapproval_amount_rules_hit_without_linked_application` 覆盖 meal。
|
||||
- [x] 新增风险规则执行测试:办公用品超过 2000 元且无申请命中。[CONCEPT: 测试方案] 证据:`test_preapproval_amount_rules_hit_without_linked_application` 覆盖 office。
|
||||
- [x] 新增风险规则执行测试:通用费用超过 2000 元且无申请命中。[CONCEPT: 测试方案] 证据:`test_preapproval_amount_rules_hit_without_linked_application` 覆盖 software。
|
||||
- [x] 新增资产同步测试:财务规则中心包含新增规则资产。[CONCEPT: 指标与验收] 证据:`test_finance_rules_use_risk_rule_scenario_categories` 断言新增财务规则资产和规则文档。
|
||||
- [x] Docker `x-financial-main` 容器内定向测试通过。[CONCEPT: 指标与验收] 证据:新增与相邻回归共 15 个后端测试通过。
|
||||
- [x] 重启后端并验证运行时健康状态。[CONCEPT: 指标与验收] 证据:`x-financial-main` 已重启并进入 healthy;真实库可查到 `rule.expense.company_preapproval_requirement`。
|
||||
@@ -1,111 +0,0 @@
|
||||
# 通知中心状态持久化概念文档
|
||||
|
||||
## 功能一句话
|
||||
|
||||
为首页小铃铛通知中心补齐服务端状态接口,让同一用户在不同电脑登录时看到一致的已读、清空和隐藏状态,并优化笔记本等小屏幕下的通知弹窗可读性。
|
||||
|
||||
## 背景与问题
|
||||
|
||||
当前小铃铛通知由前端从单据中心、个人工作台摘要等数据源即时生成,但已读与清空状态主要写入浏览器 `localStorage`。这会导致同一账号在 A 电脑清空通知后,换到 B 电脑仍然看到通知。
|
||||
|
||||
同时,通知条数较多或屏幕高度较小时,列表内容容易挤压头部操作区,通知标题与描述也容易在窄宽度下互相挤压。
|
||||
|
||||
## 目标与非目标
|
||||
|
||||
目标:
|
||||
|
||||
- 提供当前用户维度的通知状态接口。
|
||||
- 支持批量同步通知状态,至少覆盖已读与隐藏。
|
||||
- 前端优先使用服务端状态,接口不可用时保留本地降级能力。
|
||||
- 优化小屏幕通知弹窗,列表多时使用内部滚动,标题、描述与操作按钮不互相挤压。
|
||||
|
||||
非目标:
|
||||
|
||||
- 不做独立消息投递系统。
|
||||
- 不新增推送、WebSocket 或邮件通知能力。
|
||||
- 不改变通知来源生成逻辑,当前仍由单据中心和工作台摘要生成。
|
||||
|
||||
## 用户与场景
|
||||
|
||||
- 普通员工:在个人工作台查看待办、单据新消息,跨电脑登录后已读状态一致。
|
||||
- 审批人:处理待审批单据后,通知中心不因换电脑重新显示已清空内容。
|
||||
- 管理员:仍可看到系统内已有通知入口,但 admin 是否展示工作台由现有逻辑决定。
|
||||
|
||||
## 功能能力
|
||||
|
||||
- `GET /notification-states`:读取当前登录用户的通知状态集合。
|
||||
- `POST /notification-states`:批量保存当前登录用户的通知状态。
|
||||
- 状态字段:
|
||||
- `notification_id`:前端生成的稳定通知 ID。
|
||||
- `read_at`:已读时间。
|
||||
- `hidden_at`:隐藏或清空时间。
|
||||
- `context_json`:保留通知来源、类型等低风险上下文,便于排查。
|
||||
- 前端能力:
|
||||
- 打开工作台或弹窗时读取服务端状态。
|
||||
- 点击通知写入已读。
|
||||
- 清空通知写入隐藏。
|
||||
- 接口失败时仍写入本地缓存,避免用户操作失效。
|
||||
|
||||
## 方案设计
|
||||
|
||||
后端:
|
||||
|
||||
- 新增 `NotificationState` SQLAlchemy 模型。
|
||||
- 新增 `NotificationStateService`,负责按 `CurrentUserContext.username` 读写状态。
|
||||
- 新增 `notification_states` endpoint,并挂到 API v1 router。
|
||||
- 服务初始化时使用项目现有 `Base.metadata.create_all(..., tables=[...])` 模式确保表存在。
|
||||
|
||||
前端:
|
||||
|
||||
- 新增 `web/src/services/notificationStates.js` 封装接口。
|
||||
- `TopBar.vue` 将 `localStorage` 状态作为初始兜底,服务端状态返回后合并覆盖。
|
||||
- `markNotificationRead` 与 `clearAllNotifications` 做乐观更新,再异步同步服务端。
|
||||
- 对单据通知仍调用现有 `markDocumentInboxRowRead`,同时写入通知状态接口,保证跨设备一致。
|
||||
|
||||
小屏幕布局:
|
||||
|
||||
- 弹窗宽度使用 `clamp` 与 `100vw` 约束。
|
||||
- 弹窗最大高度使用 `min(..., calc(100vh - ...))`。
|
||||
- 列表作为唯一滚动区域,头部和 tab 固定在弹窗网格内。
|
||||
- 通知描述允许两行截断,避免窄屏时横向挤压。
|
||||
|
||||
## 算法与公式
|
||||
|
||||
当前功能不涉及显式数学公式。状态合并规则为:
|
||||
|
||||
$$
|
||||
visible = notification\_id \notin hiddenIds
|
||||
$$
|
||||
|
||||
$$
|
||||
unread = sourceUnread \land notification\_id \notin readIds \land notification\_id \notin hiddenIds
|
||||
$$
|
||||
|
||||
服务端状态优先,前端本地状态仅作为接口失败或首次加载前的兜底。
|
||||
|
||||
## 测试方案
|
||||
|
||||
- 后端单元测试:
|
||||
- 当前用户只能读取自己的通知状态。
|
||||
- 批量 upsert 后可读取 `read_at`、`hidden_at`。
|
||||
- 清空通知写入 hidden 状态。
|
||||
- 前端静态测试:
|
||||
- `TopBar` 引用通知状态服务。
|
||||
- 已读、清空操作会同步服务端。
|
||||
- 小屏 CSS 使用弹窗 max-height、内部滚动和移动端约束。
|
||||
- 构建验证:
|
||||
- 运行前端构建确认 Vue 与服务导入无误。
|
||||
- 在容器内运行后端定向 pytest。
|
||||
|
||||
## 指标与验收
|
||||
|
||||
- 同一用户跨电脑登录后,已读和清空状态由服务端保持一致。
|
||||
- 接口失败时用户仍可本地清空,不阻断主要流程。
|
||||
- 通知弹窗在笔记本高度下不会挤压头部按钮,列表内部滚动。
|
||||
- 通知标题、描述、时间在窄屏下不横向溢出。
|
||||
|
||||
## 风险与开放问题
|
||||
|
||||
- 当前通知本身仍由前端即时生成,服务端只保存状态,不保存完整通知正文。
|
||||
- 通知 ID 需要保持稳定,否则服务端状态无法命中;本次沿用现有 `document:` 和 `workbench:` 前缀。
|
||||
- 历史 localStorage 状态会作为首次迁移兜底,后续服务端会逐步成为主状态源。
|
||||
@@ -1,10 +0,0 @@
|
||||
# 通知中心状态持久化 TODO
|
||||
|
||||
- [x] 调研现有小铃铛通知来源、localStorage 键和单据中心已读逻辑。[CONCEPT: 背景与问题] 证据:`TopBar.vue`、`useDocumentCenterInbox.js`、`documentCenterNewState.js` 已确认。
|
||||
- [x] 新增后端通知状态模型、Schema、Service 与 API endpoint。[CONCEPT: 方案设计] 证据:`notification_states` 支持按用户保存已读与隐藏状态。
|
||||
- [x] 将通知状态接口挂载到 API v1 router,并保持当前用户隔离。[CONCEPT: 功能能力] 证据:`GET /notification-states` 与 `POST /notification-states` 已接入。
|
||||
- [x] 新增前端 `notificationStates` 服务封装读取与批量保存。[CONCEPT: 前端] 证据:服务层统一请求 `/notification-states`。
|
||||
- [x] 改造 `TopBar` 已读、清空逻辑,优先同步服务端,保留本地降级。[CONCEPT: 前端] 证据:小铃铛点击已读、清空通知都会写入状态接口。
|
||||
- [x] 优化通知弹窗笔记本与窄屏布局,避免条数多时挤压。[CONCEPT: 小屏幕布局] 证据:弹窗限制视口高度,列表滚动,描述两行截断,420px 下隐藏行箭头。
|
||||
- [x] 补充后端和前端回归测试。[CONCEPT: 测试方案] 证据:`server/tests/test_notification_states.py`、`web/tests/sidebar-document-unread-dot.test.mjs`。
|
||||
- [x] 运行容器后端定向 pytest、前端 Node 测试与前端 build。[CONCEPT: 指标与验收] 证据:pytest 2 passed;Node 4 passed;Vite build passed。
|
||||
@@ -1,86 +0,0 @@
|
||||
# 附件上传风险前置复核
|
||||
|
||||
## 功能一句话
|
||||
|
||||
报销附件上传并完成 OCR 识别后立即执行完整风险复核,提交审批时只做轻量最终校验、预算占用和流程流转。
|
||||
|
||||
## 背景与问题
|
||||
|
||||
当前报销单提交阶段会同步执行较重的风险检查,包括附件风险汇总、差旅规则、场景规则、规则中心风险、历史行为统计和风险观测写入。用户在点击提交后会等待较长时间,容易误认为页面卡住。
|
||||
|
||||
风险的主要依据来自已上传票据、OCR 识别结果、费用明细、关联申请单和员工历史行为。这些数据在附件上传完成后已经基本具备,因此完整风险复核应前移到上传完成阶段。
|
||||
|
||||
## 目标与非目标
|
||||
|
||||
目标:
|
||||
|
||||
- 附件上传成功后自动刷新费用明细、附件风险、差旅/场景/规则中心风险和 AI 预审标识。
|
||||
- 风险复核结果写回 `claim.risk_flags_json`,并持久化规则中心风险观测。
|
||||
- 提交阶段不再重复跑完整 `_run_ai_submission_review()`。
|
||||
- 提交阶段只保留草稿完整性校验、预算占用、未处理阻断风险判断、状态流转、审计日志和助手会话清理。
|
||||
|
||||
非目标:
|
||||
|
||||
- 不新增业务字段。
|
||||
- 不改变现有风险规则语义。
|
||||
- 不把提交改成真正的后端异步任务队列。
|
||||
|
||||
## 用户与场景
|
||||
|
||||
- 报销申请人:上传票据后立即看到风险建议和需补充说明,不必等到提交时才发现问题。
|
||||
- 直属领导和财务人员:收到单据时可看到提交前已生成的风险提示和用户处理结果。
|
||||
- 系统管理员:风险观测仍可进入后台统计。
|
||||
|
||||
## 功能能力
|
||||
|
||||
上传完成后:
|
||||
|
||||
- 根据 OCR 结果回填费用明细类型、日期、金额、事由等已有字段。
|
||||
- 刷新附件级 `attachment_analysis` 风险。
|
||||
- 执行报销级风险复核,并生成 `ai_pre_review` 状态。
|
||||
- 对规则中心命中的风险写入 `risk_observations`。
|
||||
|
||||
提交审批时:
|
||||
|
||||
- 如果存在高风险且用户未处理,继续阻止提交或要求说明/按职级测算。
|
||||
- 如果风险已处理,只做预算和流程流转。
|
||||
- 不再重复生成一套提交阶段风险。
|
||||
|
||||
## 方案设计
|
||||
|
||||
后端:
|
||||
|
||||
- 在 `ExpenseClaimService.upload_claim_item_attachment()` 中,OCR、附件分析和 `_sync_claim_from_items()` 完成后,调用上传后风险复核 helper。
|
||||
- 新增 helper 复用现有 `_run_ai_submission_review()` 与 `_replace_ai_pre_review_flag()`,但保持单据状态为草稿。
|
||||
- 提交阶段读取既有风险结果,只做最终阻断风险判断,不重复调用 `_run_ai_submission_review()`。
|
||||
|
||||
前端:
|
||||
|
||||
- 继续使用当前附件识别中的状态条。
|
||||
- 上传完成后通过接口返回的 `claim_risk_flags` 更新 AI 建议区和风险标识。
|
||||
- 提交时只显示轻量后台提交流程提示。
|
||||
|
||||
## 算法与公式
|
||||
|
||||
当前功能不涉及新的显式数学公式。风险评分和风险等级沿用现有规则中心、附件分析、差旅政策和风险观测逻辑。
|
||||
|
||||
## 测试方案
|
||||
|
||||
- 后端单元测试:附件上传后写入 `ai_pre_review` 和 `submission_review` 风险。
|
||||
- 后端单元测试:提交阶段不再调用完整 `_run_ai_submission_review()`。
|
||||
- 后端单元测试:上传后规则中心风险可写入 `risk_observations`。
|
||||
- 前端静态回归:提交确认仍为后台提交,不恢复阻塞弹窗。
|
||||
- 构建验证:`npm.cmd --prefix web run build`。
|
||||
|
||||
## 指标与验收
|
||||
|
||||
- 上传附件后,接口响应的 `claim_risk_flags` 包含最新复核结果。
|
||||
- 提交接口耗时不再包含完整风险复核耗时。
|
||||
- 提交后审批人仍能看到已前置生成的风险提示。
|
||||
- 后端和前端相关回归测试通过。
|
||||
|
||||
## 风险与开放问题
|
||||
|
||||
- 如果用户上传后又修改费用明细,现有 `update_claim_item()` 需要继续刷新附件风险和报销级风险。
|
||||
- 如果用户没有上传附件直接提交,提交阶段仍需要保留兜底风险复核或阻断提示。
|
||||
- 未来可进一步把上传后复核做成真正后台任务,但本次先保持同步接口返回最新风险结果。
|
||||
@@ -1,28 +0,0 @@
|
||||
# 附件上传风险前置复核 TODO
|
||||
|
||||
## 调研与契约
|
||||
|
||||
- [x] 盘点附件上传、预审、提交链路,确认完整风险复核当前在提交阶段重复执行。[CONCEPT: 背景与问题]
|
||||
- [x] 明确上传后复核 helper 的输入输出契约,不新增业务字段。[CONCEPT: 方案设计] 证据:新增 `_refresh_claim_pre_review_flags()` 复用现有风险字段。
|
||||
|
||||
## 后端实现
|
||||
|
||||
- [x] 在附件上传完成后触发报销级风险复核,并保持单据状态为草稿。[CONCEPT: 功能能力] 证据:`upload_claim_item_attachment()` 调用 `_refresh_claim_pre_review_flags()`。
|
||||
- [x] 上传后风险复核写回 `ai_pre_review` 和 `submission_review` 风险结果。[CONCEPT: 功能能力] 证据:`test_upload_attachment_refreshes_claim_pre_review` 通过。
|
||||
- [x] 规则中心风险在上传后写入 `risk_observations`,避免提交阶段集中写入。[CONCEPT: 方案设计] 证据:上传后复核复用 `_run_ai_submission_review()`,平台风险仍调用 `RiskObservationService.upsert_platform_risk_flags()`。
|
||||
- [x] 提交阶段改为读取既有风险结果,只做最终校验、预算占用和流转。[CONCEPT: 目标与非目标] 证据:`submit_claim()` 仅在缺少 `ai_pre_review` 时兜底复核。
|
||||
- [x] 保留“无附件直接提交”的兜底检查,避免绕过风险复核。[CONCEPT: 风险与开放问题] 证据:`test_submit_claim_runs_ai_review_and_routes_to_direct_manager` 通过。
|
||||
|
||||
## 前端实现
|
||||
|
||||
- [x] 确认上传完成后 UI 使用接口返回的 `claim_risk_flags` 刷新 AI 建议与行风险标识。[CONCEPT: 前端] 证据:`travel-request-detail-risk-advice.test.mjs` 通过。
|
||||
- [x] 确认提交阶段不恢复阻塞弹窗,只显示轻量后台提交提示。[CONCEPT: 前端] 证据:`travel-request-detail-submit-confirm.test.mjs` 通过。
|
||||
|
||||
## 测试与验证
|
||||
|
||||
- [x] 后端测试:附件上传后自动生成预审风险结果。[CONCEPT: 测试方案] 证据:`test_upload_attachment_refreshes_claim_pre_review` 通过。
|
||||
- [x] 后端测试:提交阶段不重复调用完整风险复核。[CONCEPT: 测试方案] 证据:`test_submit_claim_reuses_upload_pre_review_without_rerunning_review` 通过。
|
||||
- [x] 后端测试:风险观测仍被持久化。[CONCEPT: 测试方案] 证据:`test_risk_observation_storage_ready_is_cached_per_bind` 通过。
|
||||
- [x] 前端回归测试通过。[CONCEPT: 测试方案] 证据:54 个详情页风险/提交测试通过。
|
||||
- [x] `npm.cmd --prefix web run build` 通过。[CONCEPT: 测试方案] 证据:前端生产构建通过,仅保留既有 Rollup 注释与 chunk size 警告。
|
||||
- [x] Docker `x-financial-main` 容器内后端定向测试通过。[CONCEPT: 测试方案] 证据:核心上传前置复核、提交复用预审、申请/报销风险回归测试通过。
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user