81 Commits

Author SHA1 Message Date
caoxiaozhu
a12c4bea64 chore: 更新公司通信费规则表、work-log 与 superpowers specs 文档 2026-06-24 22:59:10 +08:00
caoxiaozhu
e5b03c6601 feat(web): 文档查询意图补充风险过滤与 X 天前范围
- aiDocumentQueryIntent 新增风险等级过滤(无/高/中/低/有风险)与 N 天前日期范围解析
- aiDocumentQueryModel/aiConversationHtmlRenderer 渲染适配风险过滤标签
- useWorkbenchAiDocumentQueryFlow/aiWorkbenchConversationStore 会话流转适配命令意图
- 更新 ai-document-query-model/ai-conversation-html-renderer/assistant-session-draft-delete 测试
2026-06-24 22:59:05 +08:00
caoxiaozhu
3eb78d343a feat(web): AI 工作台命令意图解析与动作策略
- 新增 workbenchIntentFrameModel,解析删除/审批/查询/咨询政策等意图帧,含风险等级、目标模式(当前上下文/筛选候选)与日期归一化
- 新增 workbenchIntentActionPolicy,按意图帧路由下一步(直通/拦截批量/上下文确认/查询候选)
- 新增 workbenchAiCommandIntentModel 与 useWorkbenchAiCommandIntents,识别草稿删除意图并解析最近草稿载荷生成引导
- usePersonalWorkbenchAiMode 接入命令意图处理,personal-workbench-ai-mode.css 适配
- 新增 command-intent-model/intent-frame-model 测试
2026-06-24 22:58:59 +08:00
caoxiaozhu
a0f6d9f702 chore: 更新 .env.example、财务规则表与 AI 意图规划开发文档 2026-06-24 21:59:15 +08:00
caoxiaozhu
bb681aa1f3 chore(repo): 移除误跟踪的 egg-info 构建产物并新增忽略规则
server/src/x_financial_server.egg-info/ 为 uv/pip 构建产物,此前被误跟踪。
本次 git rm --cached 移除跟踪(本地保留),.gitignore 新增 *.egg-info/ 规则避免后续误入库。
2026-06-24 21:59:09 +08:00
caoxiaozhu
bc560145a4 feat(web): AI 工作台意图规划与规划思考模型
- 新增 workbenchAiIntentPlannerModel,基于 LLM function_call 解析建单/草稿/提交意图,区分 model 与 rule_fallback 来源
- 新增 workbenchAiPlanningThinkingModel 合并规划思考事件流,按 eventId 去重合并
- application gate/preview 模型接入意图规划,usePersonalWorkbenchAiMode/useWorkbenchAiStewardFlow/useWorkbenchAiActionRouter 链路适配,支持上下文提交
- steward 服务与 stewardPlanModel 适配新动作结构,receipt-folder-view 微调样式
- 新增 intent-planner-model/application-context-submit/steward-actions-service 测试,更新 gate-model/action-router/plan-message-copy/fast-preview 测试
2026-06-24 21:58:46 +08:00
caoxiaozhu
5311c99d69 refactor(server): steward 决策链路改用 LangGraph 编排
- 新增 StewardGraphPlannerService,用 LangGraph 状态图编排意图识别→流程判断→模型/规则分支→兜底,替代原 planner 内线性调用
- 新增 StewardGraphRuntimeService 编排运行时决策与槽位决策;StewardActionContracts/Executor 统一动作合约与执行
- steward_intent_agent/application_fact_resolver/runtime_chat 适配图执行器,config 暴露图相关开关
- pyproject/uv.lock 新增 langgraph 依赖
- 新增 graph_planner/graph_runtime/action_executor 测试,更新 intent_agent/planner/fact_resolver/runtime_chat/reimbursement 测试
2026-06-24 21:58:35 +08:00
caoxiaozhu
545b31d32f chore(env): docker-compose 端口与服务配置微调并更新规则表与日志
- docker-compose(.full).yml 与 start.sh 微调端口/服务配置
- AGENTS.md 同步更新协作规范
- 更新交通/通信/差旅等财务规则表,补 2026-06-24 work-log
2026-06-24 12:36:03 +08:00
caoxiaozhu
8417a9f542 feat(web): 设置中心缓存管理与文件预览资产工具
- 新增 documentPreviewAssets 工具,统一从 URL/Blob/File 推断预览类型(image/pdf/file/unsupported)
- SettingsView/SettingsView.js/settingsModelHelper 新增系统缓存管理区块,调用 /settings/cache/clear 并展示清理结果;useSettings/services 适配
- WorkbenchAiFilePreviewDialog/useWorkbenchAiFilePreview 接入预览资产工具,workbenchAiComposerModel 调整文件处理
- ReceiptFolder/LogDetailView/DigitalEmployeeWorkRecords/travelReimbursementAttachmentModel 配套适配
- 新增 settings-cache-management-section 测试,更新 settings-llm/rendering/receipt-folder-view/composer-components/attachment-association 测试
2026-06-24 12:35:59 +08:00
caoxiaozhu
9a5ed0e94a feat(server): 系统缓存清理接口与 OCR 文本层兜底增强
- 新增 system_cache 模块与 POST /settings/cache/clear,管理员可一键清理 OCR 结果/运行时配置/模型失败冷却/知识库索引/地点语义等进程内缓存
- 各服务暴露 clear_*_cache 方法(ocr/runtime_settings/runtime_chat/knowledge/application_location_semantic),SettingsCacheClearRead 汇总清理项
- OCR 转图片失败时尝试用 PDF 文本层兜底构建识别文档(有效字符≥8),并写结果缓存;OcrService 暴露 clear_result_cache
- receipt_folder 车票过滤补充身份证号关键词,附件文档/操作/展示模块同步适配
- 新增 system_cache_endpoints 测试,更新 openapi_schema/ocr/receipt_folder/attachment_association_jobs 测试
2026-06-24 12:35:51 +08:00
caoxiaozhu
50d2dc579a style(web): 微调 AI 工作台样式 2026-06-24 10:43:08 +08:00
caoxiaozhu
f9553a6a1a docs: 清理过期计划文档并补全 work-log 与开发/用户文档
- 删除已落地的 improvement-roadmap、superpowers 计划与 ui-mockups 参考稿,删除早期 work-log(2026-05-06~08)
- 新增 2026-05-23 起的 work-log 与 attachment-association-background-job、reimbursement-draft-action-branching 等开发文档及用户文档
- docker-compose(.full).yml 微调服务配置
2026-06-24 10:42:57 +08:00
caoxiaozhu
ee730aa31c feat(web): AI 工作台文件预览/附件关联任务与草稿分支
- 新增 WorkbenchAiFilePreviewDialog 附件预览对话框及 useWorkbenchAiFilePreview,附件支持点击预览
- 新增 attachmentAssociationJobs/linkedReimbursementDraftJobs 前端服务与对应 composable,接入后台任务轮询与状态展示
- 新增 travelReimbursementDraftBranchModel 草稿分支模型,报销关联门控支持跳过/选择草稿
- PersonalWorkbenchAiMode 及各 composable(expense/document/steward/application-preview/attachment-association)重构适配,WorkbenchAiComposer/FileStrip 样式与交互完善
- DocumentsCenter/ReceiptFolder/TravelReimbursementCreate 等视图及 scripts 重构,风险/差旅规划/审批等工具适配
- 新增/更新前端测试:application-result-card、reimbursement-list-preview-fetch、guided-flow、composer-components 等
2026-06-24 10:42:50 +08:00
caoxiaozhu
0264a4b5b4 refactor(server): user_agent/steward/ocr 等服务重构并适配关联任务
- user_agent 拆分 application/locations/knowledge/response/review 四个子模块,接入申请位置语义与关联草稿分支
- steward planner/runtime/slot/plan_builder 决策链路重构,travel_reimbursement_calculator/orchestrator_expense_query 适配
- ocr/document_preview/document_intelligence/receipt_folder 复用预览与资产缓存,expense_claim_draft_flow/application_handoff 适配
- pyproject.toml 新增依赖,paddleocr bootstrap 脚本与 server_start.sh 调整
- 更新差旅/交通/通信等财务规则表,同步 document_intelligence/ocr/receipt_folder/user_agent 等测试
2026-06-24 10:42:24 +08:00
caoxiaozhu
332f77389d feat(server): 新增附件关联/关联报销草稿后台任务与申请位置语义
- attachment_association_jobs:从票据夹批量关联附件到报销单,识别城市/日期并创建明细项,内存态 job 跟踪
- linked_reimbursement_draft_jobs:基于申请单异步生成关联报销草稿,调用 Orchestrator 编排,区分 succeeded/failed 终态
- application_location_semantics:抽取差旅出发/到达城市、判断具体地址/业务动作等位置语义,供申请单校验复用
- router 注册两个 job 端点,新增对应 job/语义单元测试
2026-06-24 10:42:05 +08:00
caoxiaozhu
d4ff79f326 chore(agent): 新增变更日志/checkpoint skill 与 post-commit hook
- AGENTS.md 新增变更日志 Skill 规范:变更后增量更新 document/work-log,更新前先做 Git 拉取检查
- 新增 .codex/skills 下 agent-change-log、git-checkpoint-commit 两个 skill
- 新增 .githooks/post-commit 与 tools/agent-change-log 自动化脚本,提供提交后最低限度日志追加
2026-06-24 10:41:56 +08:00
caoxiaozhu
93212600eb chore(skills): add git checkpoint commit loop 2026-06-24 09:47:05 +08:00
caoxiaozhu
73966b3a7b refactor: consolidate finance workflow modules 2026-06-23 11:21:18 +08:00
caoxiaozhu
1f40ce3df3 fix(web): 孤儿申请预览消息触发重新生成可编辑表格
useWorkbenchAiApplicationPreviewFlow 识别仅含表格文案但缺 applicationPreview 载荷的孤儿消息,触发重新生成可编辑表格并提示用户,避免提交动作静默失败。
2026-06-23 09:43:29 +08:00
caoxiaozhu
f17098aa58 chore(repo): 移除误跟踪的 expense_claims 运行时票据文件
server/storage/expense_claims/ 下 30 个用户上传票据/预览图/meta 此前在 gitignore 规则生效前被误提交,运行时会持续产生内容变更。
本次 git rm --cached 移除版本跟踪(本地文件保留),配合已有 server/storage/expense_claims/ 忽略规则,避免后续误入库。
2026-06-23 09:43:07 +08:00
caoxiaozhu
8094333e3b style(web): 通知中心列表行布局重构与时间标签格式化
- TopBar 通知行改为 row-main/row-head/row-foot 结构化布局,标题加粗、分类改为 pill、箭头随标题右侧
- formatNotificationTime 改名 formatNotificationTimeLabel,新增 ISO/短日期直通匹配与超长截断兜底
- 更新 sidebar-document-unread-dot 测试
2026-06-23 09:42:56 +08:00
caoxiaozhu
0122f3b250 chore(rules): 更新交通/通信/差旅/出差等财务规则表 2026-06-23 09:42:43 +08:00
caoxiaozhu
dc4cad2baa chore(env): WEB_PORT 统一回退为 5173 并烟雾检查改为可开关
- .env.example/docker-compose(.full).yml WEB_PORT 默认值 5273→5173(Vite 默认),CORS_ORIGINS 同步
- docker-compose 注入 WEB_PORT/SERVER_PORT 环境变量,健康检查端口随之更新
- start.sh 新增 SERVER_SMOKE_CHECK_ENABLED 开关,默认关闭烟雾检查,仅健康探测
- web/web_start.sh 适配端口
- .gitignore 补充忽略 tmp/ 运行时目录
2026-06-23 09:42:40 +08:00
caoxiaozhu
e725b7f19c feat(web): 票据夹资产缓存接入与 AI 工作台附件流程完善
- ReceiptFolderView 删除票据后提示已关联附件副本保留,接入 useToast;fetchReceiptFolderAsset 加 no-store 避免预览缓存
- PersonalWorkbenchAiMode 附件区/对话气泡适配资产缓存,personal-workbench-ai-mode.css 调整布局
- usePersonalWorkbenchAiMode/useWorkbenchAiApplicationPreviewFlow/useWorkbenchAiAttachmentAssociationFlow/useWorkbenchAiStewardFlow 完善附件草稿选择与关联流程
- travelRequestDetailSmartEntryRecognition 智能识别增强,AppShellRouteView/PersonalWorkbenchView/useApplicationPreviewEditor/useTravelReimbursementSubmitComposer 等配套适配
- 新增 expense-attachment-draft-selection、receipt-folder-asset-cache、travel-request-detail-smart-entry-recognition 测试,更新 attachment-association-confirmation、expense-application-fast-preview、workbench-ai-mode-switch 测试
2026-06-23 09:42:13 +08:00
caoxiaozhu
84a8998e59 feat(server): 票据文件夹资产缓存与文档预览统一生成
- 新增 document_preview 模块,DocumentPreviewAssets 统一处理 data URL 解码、pdftoppm PNG 预览生成(poppler-data 编码)、renderer_id 标识
- receipt_folder 服务复用预览生成,缓存票据资产并提供清理;删除票据时保留已关联报销单的附件副本
- document_intelligence 新增票据预览/资产缓存接入与字段提取增强;ocr 抽取复用预览工具,附件分析/文档/操作/展示四个子模块同步适配
- receipt_folder 端点补充资产缓存头,补/扩 document_intelligence、ocr_endpoints、ocr_service、receipt_folder_service、reimbursement_endpoints 测试,新增 attachment_analysis 回归测试
2026-06-23 09:42:00 +08:00
caoxiaozhu
bc743adef3 chore(rules): 更新公司通信费报销规则表 2026-06-22 15:56:13 +08:00
caoxiaozhu
ded8b39ccb feat(web): 申请单预览编辑器增强与报销流程细节适配
- useApplicationPreviewEditor 扩展字段编辑与校验,useTravelReimbursementApplicationPreviewDateEditor 微调日期处理
- travelReimbursementExpenseQueryModel/reimbursements 服务/expenseApplicationPreview 适配工号/邮箱字段与关联动作
- useWorkbenchAiApplicationPreviewFlow/usePersonalWorkbenchAiMode 接入关联门控后的预览流转
- TravelReimbursementCreateView 调整入口,TravelReimbursementMessageItem 适配
- 新增 expense-application-fast-preview 测试,更新 attachment-association-confirmation、review-drawer-switch 测试
2026-06-22 15:56:06 +08:00
caoxiaozhu
ba444a514f feat(web): 报销单新增关联申请单门控与草稿检测流程
- 新增 travelReimbursementAssociationGateModel,查询可关联申请单/草稿报销单并生成跳过/选择/单独新建动作,区分差旅费与业务招待费类型
- travelReimbursementApplicationLinkModel 补充 buildLinkedApplicationReferenceIndex/buildRequiredApplicationActions 等关联构建逻辑
- useTravelReimbursementSuggestedActions 接入 select_required_application/skip 系列动作,'我要报销'入口改为先走关联门控
- useWorkbenchAiActionRouter 新增 SKIP_REQUIRED_APPLICATION_LINK/SKIP_REIMBURSEMENT_DRAFT_CHECK 动作分发
- useWorkbenchAiExpenseFlow 暴露 startAiReimbursementAssociationGate,stewardPlanModel 待处理流程适配
- 新增 workbench-ai-action-router、workbench-ai-reimbursement-association-gate 测试并更新 guided-flow、steward-plan 测试
2026-06-22 15:55:59 +08:00
caoxiaozhu
aa965da69d feat(server): 报销单输出工号/邮箱并扩展申请人邮箱前缀匹配
- ExpenseClaimRead 新增 employee_no/employee_email 字段,ExpenseClaim 模型补对应只读属性
- expense_claim_access_policy 在姓名匹配未果时,按 candidate@% 邮箱前缀匹配 Employee.email,命中唯一记录即返回
- test_backend_pagination/test_expense_claim_service 补充工号/邮箱字段断言与邮箱前缀匹配用例
- 更新公司通信费报销规则表
2026-06-22 15:55:48 +08:00
caoxiaozhu
1b04ee1c4c fix(web): restore travel detail child component styles 2026-06-22 13:34:12 +08:00
caoxiaozhu
103f225f54 chore(rules): 更新差旅/交通/通信等财务规则表 2026-06-22 12:02:11 +08:00
caoxiaozhu
e42dedaba1 docs: 同步 Docker 编排说明并补充重构计划与 UI 参考
- README 与 docker/README 更新双 compose 文件说明:docker-compose.yml 仅主应用、docker-compose.full.yml 完整栈
- 新增 docs/superpowers/plans 800 行重构实施计划,规划前后端代码体量护栏与按职责拆分路径
- 新增 docs/ui-mockups 附件关联企业版 UI 参考稿
2026-06-22 12:02:08 +08:00
caoxiaozhu
607e127f59 chore(docker): 拆分主应用编排与完整本地栈
- 新增 docker-compose.full.yml:编排 main+postgres+qdrant+onlyoffice 完整本地开发栈,依赖与 DATABASE_URL 由 compose 注入
- docker-compose.yml 精简为仅启动主应用,ONLYOFFICE/QDRANT 相关变量改为空值占位,便于复用外部依赖
- docker-compose.postgres.yml 主应用补充 POSTGRES_*/DATABASE_URL 环境变量
2026-06-22 12:02:03 +08:00
caoxiaozhu
6d33ba5742 refactor: enforce 800 line source limits 2026-06-22 11:58:53 +08:00
caoxiaozhu
08a4fa3577 style(web): AI 工作台附件卡片样式与交互适配
- personal-workbench-ai-mode.css 新增 workbench-ai-file-card 系列样式,file-strip 改为 flex-wrap 左对齐排布卡片,移除按钮样式随卡片重构
- workbench-ai-mode-switch 测试补充附件卡片相关断言
2026-06-21 23:24:36 +08:00
caoxiaozhu
d660a961fb feat(web): AI 工作台附件改为卡片化展示并支持单项移除
- PersonalWorkbenchAiMode 附件区由计数条改为按类型图标/名称/类型标签的卡片列表,支持单项移除(removeAiModeFile),复用 buildFileIdentity 作为 key
- resolveAiComposerFileType 按 pdf/图片/表格/文档/压缩包/文件归类,分别对应图标与色调
- .gitignore 补充忽略 server/storage/receipt_folder/ 运行时票据存储目录
2026-06-21 23:24:16 +08:00
caoxiaozhu
669d22e71f feat(web): 差旅领导意见事件结构化与申请审批信息增强
- applicationApproval 新增按日期/时间/审批角色拆分格式化,buildLeaderApprovalEvents 补充 dateLabel/timeLabel/roleLabel 字段
- TravelRequestDetailView 领导意见事件改为日期+时间+审批人结构化展示,travel-request-detail-view.css 重构对应样式
- travelReimbursementAttachmentModel 微调附件标识,同步更新 application-approval-info、travel-request-detail-leader-approval、attachment-association-confirmation 测试
- 更新公司通信费报销规则表
2026-06-21 23:24:09 +08:00
caoxiaozhu
88e91a5900 feat(ocr): PDF 文本层可用时跳过 worker 调用并补装 poppler-data
- OcrService 提取 PDF 文本层后若有效字符达到阈值,直接构建文档并写入结果缓存,不再触发 OCR worker,仅无文本层时才解析 python_bin/worker_path 调用 worker
- _build_text_layer_document 复用 AggregatedOcrDocument 聚合文本层片段,_has_usable_pdf_text_layer 基于 meaningful_char_count 判定
- docker-compose 与 paddleocr bootstrap 脚本补装 poppler-data,保证 PDF 文本层抽取的中文编码正确
- 新增文本层直取与运行时依赖两项 ocr_service 单测
2026-06-21 23:23:59 +08:00
caoxiaozhu
1986b0d945 style(web): 移除顶栏 AI 快捷操作区并优化差旅领导意见事件样式
- TopBar 移除 AI 模式下的公司切换/AI 模式切换快捷操作块及 showAiModeUtilityActions 计算属性,清理 top-bar.css 对应样式
- TravelRequestDetailView 领导意见事件重构为状态/意见/审批人结构化布局,travel-request-detail-view.css 补充对应样式
- 同步更新 topbar-ai-mode-switch、ai-sidebar-rail-mode、travel-request-detail-leader-approval 测试
2026-06-21 22:56:34 +08:00
caoxiaozhu
24b5b71b0f feat(web): 差旅申请详情进度 viewer 与审批/加载态组件增强
- 新增 requestProgressViewer,申请单在直属领导审批视角下将当前步骤展示为'等待批复',travel-request-detail/app-shell/useRequests 接入
- TravelRequestApprovalDialog 增强审批交互,TableLoadingState 补充表格加载占位,ConfirmDialog 扩展确认对话框能力
- useAppShell/useRequests/AppShellRouteView 配套适配申请详情跳转与会话状态
- 同步更新 requestProgressSteps、travel-request-detail-leader-approval、assistant-session-draft-delete、documents-center-status-filter、app-shell-financial-assistant-entry、request-progress-viewer 等测试
2026-06-21 22:49:58 +08:00
caoxiaozhu
8b3495455b feat(web): AI 文档详情引用解析与查询卡片增强
- 新增 aiDocumentDetailReference,统一解析 #ai-open-document-detail / #ai-open-application-detail 引用,兼容 A/R/D 短格式与 AP-/RE-/AD- 旧格式单号,提供 isBusinessDocumentReference 判定
- aiDocumentQueryModel 文档卡片接入详情引用,按申请单/报销单生成对应 href,HTML 渲染器识别单据记录表格并生成卡片链接
- PersonalWorkbenchAiMode 处理文档详情点击跳转,卡片样式重构为结构化布局并更新背景资源
- expenseApplicationPreview 补充事由字段,同步新增/更新 ai-document-detail-reference、document-query-model、html-renderer、workbench-ai-mode 等测试
- 更新公司通信费报销规则表
2026-06-21 22:49:53 +08:00
caoxiaozhu
3b74a330a3 feat(web): AI 文档查询卡片重构与单号判定统一
- documentClassification 抽出 isApplicationDocumentNo,统一兼容 AP-/APP- 旧格式与 A+8 新格式,aiDocumentQueryModel 复用
- aiDocumentQueryModel 文档卡片改为结构化字段布局(单据类型/金额/申请人/编号/操作),新增查询范围摘要区,渲染走 HTML 信任块
- AppShellRouteView/useAppShell/useRequests/detailAlerts/riskVisibility 等差旅详情模型适配单号判定
- 同步更新 ai-document-query-model/workbench-ai-mode-switch 测试,新增 document-classification 测试
2026-06-20 22:04:37 +08:00
caoxiaozhu
8158716e23 test(server): 适配 A/R/D 紧凑单号格式
- approval_routing/service/user_agent 测试中报销单查询统一兼容 RE- 旧格式与 R+8 新格式,申请单单号断言改为短格式
- generate_claim_no 用例重命名为短前缀校验,正则改为 R[A-HJ-NP-Z2-9]{8}
- 同步更新差旅/交通/通信等财务规则表
2026-06-20 22:04:31 +08:00
caoxiaozhu
0cda750ff0 feat(web): AI 工作台会话与文档卡片渲染增强
- aiConversationHtmlRenderer 识别单据记录类表格并渲染为卡片列表,新增删除申请单详情的禁用占位链接
- aiWorkbenchConversationStore 增加草稿删除后会话链接失效处理,避免点击已删除单据跳转
- aiApplicationPreviewActions 调整提交/草稿调用路径,PersonalWorkbenchAiMode 接入新的会话存储与渲染
- ConfirmDialog/TravelRequestDeleteDialog/useAppShell/AppShellRouteView 配套适配,同步更新相关前端测试
2026-06-20 21:44:16 +08:00
caoxiaozhu
81e990ab72 feat(server): 申请单支持草稿保存并统一删除权限口径
- user_agent_application 新增草稿分支:识别'保存草稿/存草稿/先保存'等意图,复用可编辑记录更新或建草稿,提交前单据重叠仍拦截
- 草稿态返回单号与待提交提示,submit 仅在确认提交分支触发,避免草稿进入审批流
- reimbursements 删除接口文案与判定统一为系统管理员可删、申请人删自有草稿/退回单,申请单判定改用 is_application_claim_no
- 更新财务规则表与 reimbursement 端点测试
2026-06-20 21:44:12 +08:00
caoxiaozhu
47c6a4bb73 refactor(server): 单号规则收紧为 A/R/D+8 位紧凑格式
- DOCUMENT_NUMBER_PREFIXES 改为 A/R/D,新增短格式与旧格式正则并存识别,提取正则加边界锚定避免误匹配
- build_document_number 去掉时间戳段,统一生成 A+token 等紧凑单号,is_application_claim_no 兼容旧 AP-/APP- 前缀
- access_policy/status_registry/reimbursements/expense_claims/budget_support 统一复用 is_application_claim_no 判定申请单
- 同步 document_numbering 单元测试覆盖新旧两种格式
2026-06-20 21:44:06 +08:00
caoxiaozhu
96c2e1099a feat(web): 统一平台管理员判定与 AI 工作台申请预览动作接入
- authUser 抽出 resolveAuthUserAdminFlag,统一 isAdmin 解析(含 superadmin、role_codes、中英文角色名),accessControl 复用同一逻辑
- 登录态、应用外壳路由、系统状态接入统一管理员判定,LoginView 与相关 composable 配套调整
- AI 工作台申请提交改为调用新的 /application-preview-action 接口,草稿保存仍走 orchestrator;预审模型补充重叠冲突提示与阻断判断
- 同步更新 accessControl/api-request/ai 预览动作等前端测试
2026-06-20 14:42:04 +08:00
caoxiaozhu
729d833edb feat(server): 新增申请核对预览快速建单接口与平台管理员判定统一
- reimbursements 新增 POST /application-preview-action,AI 工作台表格核对后直接走 UserAgentService 建单/提交,免去通用 Orchestrator 编排
- 平台管理员判定统一抽取 PLATFORM_ADMIN_IDENTITIES 常量,identity 与 role_codes 均支持 admin/superadmin,含 header 开关
- docker-compose 镜像补装 openssh-server
- 同步更新差旅/交通/通信等财务规则表与 reimbursements 端点测试
2026-06-20 14:41:59 +08:00
caoxiaozhu
304bbe1fd4 feat(web): 工作台 AI 模式报销预审与文档查询模型拆分
- 新增 aiApplicationPrecheckModel/aiDocumentQueryModel/aiApplicationPreviewActions/aiConversationHtmlRenderer 四个独立模型与服务,按职责从主组件拆出
- PersonalWorkbenchAiMode 接入拆分后的预审、文档查询与 HTML 渲染逻辑,配合 markdown 工具增强结构化展示
- 文档中心与归档筛选、风险可见性、申请预览等工具同步适配,补充对应单元测试
- 新增 AI 文档卡片背景资源
2026-06-20 10:17:37 +08:00
caoxiaozhu
3d69f8501f feat(risk): 申请单阶段风险可见性细化与规则表更新
- 申请单阶段将 policy/trip/amount 类风险对申请人可见、可自行修正,画像/审批流程类仍走领导可见
- 平台风险标记与语义推断统一采用该策略,更新对应单元测试
- 风险规则中 city_mismatch 等城市匹配规则移除 expense_application 阶段,仅保留 reimbursement
- 同步更新交通/通信/差旅/出差等财务规则表
2026-06-20 10:17:18 +08:00
caoxiaozhu
4d04f4e7af chore(gitignore): 忽略 .env 防止敏感配置入库
将 .env 从版本库移除(本地文件保留),并补充 .env.local 等本地变体忽略规则,
避免数据库密码、密钥等敏感配置进入 git 历史。
2026-06-18 22:14:53 +08:00
caoxiaozhu
3131112952 style(web): 调整 AI 模式用户消息气泡布局与引用图标
- 用户消息气泡的操作区与时间右对齐
- 引用按钮图标由 mdi-reply 改为 mdi-format-quote-open
2026-06-18 22:13:09 +08:00
caoxiaozhu
a2f67af13e docs: 新增 X-Financial 改进路线图 2026-06-18 22:12:38 +08:00
caoxiaozhu
0cde1f8990 feat(web): 工作台 AI 模式与差旅/风险建议交互优化
- 新增 PersonalWorkbenchAiMode 组件、AI 侧边栏与 orb 机器人视觉资源
- 新增 aiApplicationDraftModel / aiExpenseDraftModel / aiWorkbenchConversationStore
  及业务准入 aiSidebarBusinessAccess,支撑 AI 模式下的申请与报销草稿
- 顶栏、侧边栏、工作台样式重构,适配 AI 模式切换与响应式布局
- 同步 steward plan/off_topic、差旅报销引导流、风险建议卡片等测试
2026-06-18 22:12:24 +08:00
caoxiaozhu
a6674a1e76 feat(steward): off_topic 场景细分与引导回复
- 将业务无关输入细分为 greeting / meaningless / off_business 三类场景
- 新增 StewardOffTopicAgent,用 function calling 生成管家语气引导回复
- steward endpoint 与 user_agent_application 串联 off_topic 引导话术
- 补充 planner 与 user agent 的 off_topic 覆盖测试
2026-06-18 22:12:10 +08:00
caoxiaozhu
127d603e7d feat(ontology): 仅放行财务业务相关问题的信号校验
- 新增 _has_supported_business_signal,在加载目录前拦截非财务问题并抛错
- 同步重构 ontology 服务测试覆盖业务信号判定分支
2026-06-18 22:12:00 +08:00
caoxiaozhu
3f17619e0c fix(auth): 登录目录就绪幂等化与并发控制
- employee/settings/user_session_metrics 的 ensure_*_ready 改为按 bind 缓存 + 锁,
  避免每次登录重复建表与并发场景下的竞态
- auth 登录链路先查员工再降级触发目录就绪,并吞掉查询期 SQLAlchemy 异常
- 默认管理员账号由 superadmin 迁移为 admin,兼容历史账号回填
- 补充登录降级与设置持久化相关测试
2026-06-18 22:11:53 +08:00
caoxiaozhu
59ba76c74a feat(startup): 服务端启动 bootstrap 与缓存预热
- 新增 STARTUP_BOOTSTRAP_ENABLED / STARTUP_CACHE_WARMUP_ENABLED 配置开关
- lifespan 拆分 bootstrap 步骤并后台线程预热缓存,失败可降级继续启动
- server_start.sh / web_start.sh 扩展 SERVER_PORT、启动与调度开关的 env 覆盖
- bootstrap_paddleocr_mobile.sh 改用 python3 并补充 poppler-utils 依赖
- 补充启动 bootstrap 与 env 覆盖优先级测试
2026-06-18 22:11:37 +08:00
caoxiaozhu
35372c6661 feat(rules): 更新差旅与通信费用等财务规则表 2026-06-18 22:11:13 +08:00
caoxiaozhu
38653fa365 chore(storage): 清理用户历史报销票据附件
移除测试期残留的 receipt_folder 附件与预览文件,后续通过归档目录维护。
2026-06-18 22:11:10 +08:00
caoxiaozhu
c28e99b714 chore(gitignore): 忽略 .nezha/ 与 .omo/ 本地工具目录 2026-06-18 22:11:06 +08:00
caoxiaozhu
43432534d8 feat(steward): 前端支持 off_topic 与引导话术
- assistantSessionScope.js:新增 ASSISTANT_SCOPE_ACTION_FILL_COMPOSER 常量
- assistantSuggestedActionPrefill.js:识别 fill_composer 与 payload.fill_text
- stewardPlanModel.js:normalizeStewardPlan 透传 suggestedPrompts;
  buildStewardPlanMessageText / buildStewardSuggestedActions
  新增 off_topic 分支,按钮填充输入框不提交
- useStewardPlanFlow.js:isPendingStewardActionMessage 排除 off_topic
- steward-plan-off-topic.test.mjs:覆盖 normalize/文案/按钮/兼容路径
2026-06-18 14:15:30 +08:00
caoxiaozhu
cce19e4c40 feat(steward): 拦截业务无关输入返回 off_topic 计划
- schemas/steward.py:StewardPlanResponse 新增 suggested_prompts 字段
- steward_planner.py:新增 STEWARD_BUSINESS_SIGNAL_KEYWORDS 与
  _is_business_irrelevant_input 守卫,在 build_plan 入口前置;
  新增 _build_off_topic_plan 构造 plan_status=off_topic 的引导计划
- steward_intent_agent.py:system prompt 追加业务无关约束
- test_steward_planner.py:覆盖 123/你好/纯标点走 off_topic,
  并验证正常业务输入不受守卫影响
2026-06-18 14:15:20 +08:00
caoxiaozhu
b8915a29c0 chore(storage): 归档用户报销票据附件 2026-06-17 14:39:41 +08:00
caoxiaozhu
4199feb681 test: 同步报销审批流与预算分析测试
- 新增预算审批合并、风险标记去重与占位条目校验用例
- 补充预算分析对当前审核人与财务的可见性断言
- 调整单据删除权限测试以匹配 admin 限制
2026-06-17 14:39:26 +08:00
caoxiaozhu
0fac8b615f feat(web): 优化差旅详情、风险建议卡片与文档中心交互
- 拆分阶段风险建议卡片样式到独立文件
- 完善差旅申请审批对话框与详情视图交互
- 调整文档中心列表共享样式与状态筛选
- 同步应用外壳、视图初始化与系统状态 composables
2026-06-17 14:39:12 +08:00
caoxiaozhu
a3e5295915 feat(rules): 更新财务差旅与通信费用规则表 2026-06-17 14:38:51 +08:00
caoxiaozhu
1f4681f486 feat(claim): 重构报销审批流并收敛风险标记
- 直属领导兼任部门 P8 预算审批人时合并预算审批,直接流转至财务审批
- 预算超过警戒值时强制要求预算管理者填写审批意见
- 新增风险标记去重工具,消除各审核阶段重复风险卡片
- 新增工作流修复 Mixin,纠正重复预算审批阶段的历史数据
- 收紧单据删除权限至 admin,放宽预算分析可见范围至当前审核人
- 提交校验放宽已上传票据条目的 OCR 字段缺失并忽略尾部占位条目
2026-06-17 14:38:07 +08:00
caoxiaozhu
09a66c72cb chore: 将 web 端口由 5173 调整为 5273 2026-06-17 14:37:50 +08:00
caoxiaozhu
0d525fa64c chore: 忽略 .codex-temp 工具临时目录 2026-06-17 14:34:36 +08:00
caoxiaozhu
470f343b29 fix(expense): narrow travel route risk indicators 2026-06-17 09:36:24 +08:00
caoxiaozhu
9f7b8b46a3 Refine travel reimbursement steward flow
Align planner, runtime rules, and policy assets so travel guidance
matches the updated reimbursement workflow.
2026-06-15 22:55:18 +08:00
caoxiaozhu
792741709a fix(claim): align risk advice with expense rows 2026-06-15 20:53:48 +08:00
caoxiaozhu
5747e85acf fix(risk): restore upload-time rule center review 2026-06-15 20:20:55 +08:00
Codex
8b952c9a26 refactor(travel): split reimbursement create workflow
完整修改内容:

- 拆分 TravelReimbursementCreateView:提取审核面板纯模型、消息操作、建议动作处理、生命周期 watcher/UI 映射、小财管家运行时、续办流程和运行时文本模型,减少主组件继续堆叠业务分支。
- 调整申请预览链路:新增本地申请意图 gate,完善复杂差旅申请的大模型复核判断、交通方式缺失/候选识别、规则中心交通费用预估合并和申请冲突处理。
- 优化小财管家流程:抽出 steward typewriter 分段策略,避免 Markdown 表格逐字闪烁;补齐跨助手 carry、字段补齐续办、文本确认提交和行程规划推荐动作。
- 调整消息与样式:移除申请预览日期 chip 样式,收敛申请卡片/报销草稿消息的展示与复制、朗读、反馈入口逻辑。
- 更新测试:将源码锚点迁移到新模块,覆盖申请预览、提交确认、小财管家续办、引导流和审核抽屉相关断言。

验证:

- node --check web/src/views/scripts/TravelReimbursementCreateView.js 及新增拆分模块
- npm --prefix web run build
- node --test web/tests/expense-application-fast-preview.test.mjs web/tests/expense-application-submit-rich-confirm.test.mjs web/tests/travel-reimbursement-guided-flow.test.mjs

说明:

- 后端/规则/容器配置/Audit 页面等工作区已有改动未纳入本提交。
- 容器内后端定向 pytest 曾运行 timeout 180s /tmp/x-financial-server-venv/bin/pytest -q <相关后端测试>,180 秒超时且超时前已有失败标记,未作为通过项记录。
- TravelReimbursementCreateView 当前仍超过 800 行,后续仍需继续拆分;本提交先把新增职责模块控制在 800 行内,阻止主类/主模块继续膨胀。
2026-06-13 14:53:23 +00:00
336fee9d93 chore: 忽略 .superpowers 工具缓存目录 2026-06-12 09:43:53 +08:00
caoxiaozhu
25724c354f feat: 同步报销流程与工作台改动 2026-06-09 08:32:00 +00:00
caoxiaozhu
e124e4bbcb feat: 报销审批流重构与管家计划全链路贯通
- 重构报销状态注册表、审批流路由与平台风险标记
- 完善管家意图规划器与模型计划构建器全链路
- 新增 OCR Worker 脚本、数据库会话管理与通知状态
- 优化文档中心、日志视图、预算中心与员工管理交互
- 增强工作台摘要、图标资源与全局主题样式
- 补充审批路由、状态注册、OCR 服务与管家规划器测试覆盖
2026-06-06 17:19:07 +08:00
caoxiaozhu
f60cebadb8 feat: 小财管家意图规划与报销提交编排增强
- 完善管家意图识别、模型计划构建与规划器调度
- 重构差旅报销提交编排器与管家计划流程前端交互
- 优化报销消息项样式与文档中心视图
- 新增小财管家与附件上传风险前置复核设计文档
- 补充管家规划器与文档中心测试覆盖
2026-06-04 14:25:14 +08:00
caoxiaozhu
1cbf3fee44 feat: 报销预审会话状态管理与工作台交互增强
- 新增差旅报销会话状态管理与对话模型重构
- 增强风险观测服务与运行时聊天上下文作用域
- 优化工作台图标资源、助理意图识别与摘要工具
- 完善报销创建视图样式与差旅详情页标准调整交互
- 补充风险观测、运行时聊天与报销端点测试覆盖
2026-06-04 11:03:29 +08:00
caoxiaozhu
87da5df91b feat: 风险可见性控制与差旅详情页交互优化
- 新增风险可见性工具函数与风险日趋势图表组件
- 优化差旅请求详情页费用模型与视图交互
- 完善顶部导航栏样式与应用壳路由逻辑
- 补充风险可见性、风险看板与差旅详情测试覆盖
2026-06-03 22:15:45 +08:00
739 changed files with 97045 additions and 25718 deletions

View File

@@ -0,0 +1,135 @@
---
name: agent-change-log
description: Use when working in X-Financial after bug fixes, new features, refactors, behavior changes, documentation edits, config edits, concurrent-agent Git commits, or any codebase modification that should leave an incremental human-readable work log.
---
# Agent Change Log
## Overview
This skill keeps a living, incremental work log for X-Financial. After each meaningful modification batch, write down what changed locally, what other agents already committed upstream, what was operated on, what remains uncertain, and what should happen next.
The log should sound like a careful teammate writing for tomorrow's teammate: concrete, warm, and honest.
## When To Use
- After fixing a bug, adding a feature, refactoring, changing behavior, editing documentation, or changing config.
- Before the final response for any turn that changed files.
- 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 use for read-only investigation with no file changes unless the user explicitly asks for a record.
## Log Location
Use one file per day:
```text
document/work-log/YYYY-MM-DD.md
```
If the file does not exist, create it. If it exists, incrementally update it. Never replace the whole file just to add a new entry.
## Required Structure
Each daily file must use these sections in this order:
```markdown
# YYYY-MM-DD 工作日志
## 当日工作内容
## 遗留问题
## TODO
```
Keep this order stable. Add entries under the existing headings instead of creating duplicate headings.
## Required Git Check
Before writing or updating the daily 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}` and the blocker in `遗留问题`.
- If there is no upstream branch, record that fact in `遗留问题` and continue with local-only logging.
- When `HEAD..@{u}` has commits, summarize those commits under `当日工作内容` 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.
## 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. Append a new timestamped bullet under `## 当日工作内容`.
4. Mention Git commits, changed files or modules, the operation, the intent, and the verification result.
5. Add or update `## 遗留问题` whenever there is risk, uncertainty, skipped verification, design debt, Git divergence, or a likely follow-up.
6. Add or update `## TODO` with checkbox items.
7. When a TODO is completed, keep it in place and mark it as:
```markdown
- [x] ~~任务内容~~(完成于 HH:MM证据...
```
## 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 "你到底改了啥?"
- Leftover issues should include a suggested next step, not only a complaint.
## Work Content Template
```markdown
- HH:MM我完成了 <本次修改目标>。
- Git 提交检查:<git fetch 后 HEAD..upstream 与 upstream..HEAD 的结果;没有就写未发现 upstream 或本地 ahead 新提交>。
- 修改:<文件/模块><做了什么>。
- 操作:<运行了什么命令、迁移了什么状态、重启了什么服务等>。
- 验证:<测试/构建/检查结果;如果没跑,说明原因>。
- 影响:<用户可见变化或工程边界变化>。
```
## Leftover Issue Template
```markdown
- HH:MM<遗留问题或风险>。建议下一步 <具体建议>。
```
## TODO Template
```markdown
- [ ] <下一步动作>来源HH:MM <上下文>
- [x] ~~<已完成动作>~~(完成于 HH:MM证据<命令/文件/结果>
```
## Final Response Checklist
Before saying work is complete:
- Today's `document/work-log/YYYY-MM-DD.md` exists.
- The Git check ran, and upstream plus local-ahead commits from other agents were summarized or explicitly marked as absent.
- `当日工作内容` includes this modification batch.
- `遗留问题` is either updated with real risks or explicitly says no new leftover issue for this batch.
- `TODO` contains new follow-ups and completed items are checked with evidence.
- The final response mentions that the work log was updated.

View File

@@ -0,0 +1,4 @@
interface:
display_name: "Agent Change Log"
short_description: "Record X-Financial changes plus upstream/local commits in the daily work log"
default_prompt: "Use $agent-change-log after a bug fix, feature, refactor, upstream/local commit check, or other file modification to update document/work-log/YYYY-MM-DD.md incrementally."

View 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.

View 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."

View 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())

51
.env
View File

@@ -1,51 +0,0 @@
APP_NAME=X-Financial
APP_ENV=local
APP_DEBUG=true
API_V1_PREFIX=/api/v1
SETUP_COMPLETED=true
VITE_SETUP_COMPLETED=true
COMPANY_NAME=YGSOFT
COMPANY_CODE=123
ADMIN_EMAIL='admin@admin.com'
VITE_COMPANY_NAME=YGSOFT
VITE_COMPANY_CODE=123
VITE_ADMIN_EMAIL='admin@admin.com'
# Admin login credentials are stored separately under server/.secrets/
WEB_HOST=10.10.10.122
WEB_PORT=5173
VITE_WEB_HOST=10.10.10.122
VITE_WEB_PORT=5173
SERVER_HOST=0.0.0.0
SERVER_PORT=8000
VITE_SERVER_HOST=0.0.0.0
VITE_SERVER_PORT=8000
SERVER_STARTUP_TIMEOUT=300
SERVER_BLOCKING_STARTUP_TIMEOUT=12
VITE_API_BASE_URL=/api/v1
VITE_AUTH_IDLE_TIMEOUT_MINUTES=30
ONLYOFFICE_ENABLED=true
ONLYOFFICE_PUBLIC_URL=http://www.caoxiaozhu.com:8082
ONLYOFFICE_BACKEND_URL=http://main:8000
ONLYOFFICE_JWT_SECRET=change-me-onlyoffice
HERMES_AGENT_SHARED_TOKEN=change-me-hermes
POSTGRES_HOST=10.10.10.189
POSTGRES_PORT=5432
POSTGRES_DB=postgres
POSTGRES_USER=root
POSTGRES_PASSWORD=8811614287327Leo
VITE_POSTGRES_HOST=10.10.10.189
VITE_POSTGRES_PORT=5432
VITE_POSTGRES_DB=postgres
VITE_POSTGRES_USER=root
DATABASE_URL='postgresql+psycopg://root:8811614287327Leo@10.10.10.189:5432/postgres'
SQLALCHEMY_ECHO=false
REDIS_URL=
VITE_REDIS_URL=
CORS_ORIGINS='["http://10.10.10.122:5173"]'

View File

@@ -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
@@ -48,4 +49,8 @@ SQLALCHEMY_ECHO=false
REDIS_URL=
VITE_REDIS_URL=
OCR_DEVICE=
OCR_TIMEOUT_SECONDS=180
OCR_MAX_CONCURRENT_WORKERS=1
CORS_ORIGINS='["http://127.0.0.1:5173","http://localhost:5173","http://0.0.0.0:5173"]'

9
.githooks/post-commit Executable file
View File

@@ -0,0 +1,9 @@
#!/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 \
--event "post-commit hook" \
>/tmp/x-financial-agent-change-log-hook.log 2>&1 || true

24
.gitignore vendored
View File

@@ -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-temp/
.superpowers/
*.log
.DS_Store
Thumbs.db
@@ -16,3 +25,16 @@ __pycache__/
server/.venv/
server/.venv-ocr312
server/.secrets/
server/logs/
server/storage/expense_claims/
server/storage/finance_reports/
server/storage/receipt_folder/
test-results/
.codex-remote-attachments/
tmp-*.png
tmp/
.nezha/
.omo/
.env
.env.local
.env.*.local

View File

@@ -5,6 +5,15 @@
- 所有分析、解释、计划、提交说明和最终回复默认使用简体中文。
- 技术结论要直击重点,必要时给出可验证的文件、命令或测试结果。
## 变更日志 Skill 规范
- 每次修复 bug、新增功能、重构、修改配置或编辑项目文档后必须调用项目级 Skill `agent-change-log`,并增量更新 `document/work-log/YYYY-MM-DD.md`
- 更新日志前必须先执行 Git 拉取检查:默认运行 `git fetch --all --prune``git status -sb``git log HEAD..@{u}``git log @{u}..HEAD`;如果工作区干净且只落后上游,可以 `git pull --ff-only`。发现其他智能体已提交到上游或本地 ahead 提交时,要先把这些提交摘要分析进当天日志。
- 自动化触发由 `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` 后,提交后才会自动追加最低限度日志。
- 如果当前环境无法写 `.git` 或无法执行 `git fetch`,必须把这个限制写进当天日志;不能把 Skill/AGENTS 规则误当成已经有后台自动化。
- 日志必须保留 `当日工作内容``遗留问题``TODO` 三块TODO 使用 Markdown checkbox完成项勾选并用删除线保留历史。
- 记录要写清楚具体时间、改了什么、操作了什么、验证了什么、还遗留什么问题,以及建议下一步怎么处理。
## 通用代码拆分规范
无论写前端、后端还是算法代码,都必须主动避免“所有方法堆在一个类里 / 一个组件里 / 一个模块里”的写法。遇到类、组件或核心模块持续变大时,优先按职责拆分,而不是继续追加方法和状态。
@@ -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 网络中依赖服务在线。
- **每次重构后至少运行对应服务的定向测试**;涉及公共协议时补充端到端或接口测试。

View File

@@ -1,45 +1,50 @@
# X-Financial
项目结构已按前后端拆开:
- `web/`:前端工程(当前 Vue + Vite 项目)
- `server/`:后端工程目录
- `docs/`:方案和阶段文档
- `UI/`:界面参考稿
- `document/`:业务文档
根目录统一环境变量:
- `.env`
- `.env.example`
这里集中维护:
- 前端启动端口
- 后端启动端口
- PostgreSQL 连接参数
- `DATABASE_URL`
- `REDIS_URL`
从根目录统一启动:
```bash
./start.sh
```
可选模式:
```bash
./start.sh web
./start.sh server
./start.sh all
```
根目录 `start.sh` 是统一编排入口;前端和后端的子启动脚本分别是 `web/web_start.sh``server/server_start.sh`
手动进入前端目录
```bash
cd web
npm run dev
```
# X-Financial
项目结构已按前后端拆开:
- `web/`:前端工程(当前 Vue + Vite 项目)
- `server/`:后端工程目录
- `docs/`:方案和阶段文档
- `UI/`:界面参考稿
- `document/`:业务文档
根目录统一环境变量:
- `.env`
- `.env.example`
这里集中维护:
- 前端启动端口
- 后端启动端口
- PostgreSQL 连接参数
- `DATABASE_URL`
- `REDIS_URL`
从根目录统一启动:
```bash
./start.sh
```
可选模式:
```bash
./start.sh web
./start.sh server
./start.sh all
```
根目录 `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
cd web
npm run dev
```

144
docker-compose.full.yml Normal file
View 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:

8
docker-compose.gpu.yml Normal file
View File

@@ -0,0 +1,8 @@
services:
main:
gpus: all
shm_size: "8gb"
environment:
NVIDIA_VISIBLE_DEVICES: all
NVIDIA_DRIVER_CAPABILITIES: compute,utility
OCR_DEVICE: "${OCR_DEVICE:-gpu:0}"

View File

@@ -0,0 +1,36 @@
services:
main:
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
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
volumes:
postgres-data:

View File

@@ -1,38 +1,37 @@
services:
main:
image: x-financial-dev:latest
container_name: x-financial-main
restart: unless-stopped
depends_on:
onlyoffice:
condition: service_started
qdrant:
condition: service_started
environment:
WEB_HOST: 0.0.0.0
SERVER_HOST: 0.0.0.0
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_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
- >
services:
main:
image: x-financial-dev:latest
container_name: local-x-financial-linux
restart: unless-stopped
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:-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: "${QDRANT_URL:-}"
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 fonts-noto-cjk fonts-noto-cjk-extra &&
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">'
@@ -48,63 +47,24 @@ services:
> /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
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:
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
networks:
financial-internal:
name: financial-internal

View File

@@ -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`.
## Start
```bash
cp .env.example .env
docker compose up -d
```
Open:
```text
http://<your-linux-host>:5173
```
## Container Layout
- `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
```
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.
## Persistence
The PostgreSQL data directory is stored in the named volume `postgres_data`.
## Notes
- 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.
# Docker Compose
X-Financial 现在按运行依赖分成两层 Docker Compose
- `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
```
默认只会启动:
```text
main
```
打开:
```text
http://<your-linux-host>:5273
```
这条路径不会主动拉起本地 PostgreSQL、Qdrant 或 ONLYOFFICE。
数据库、ONLYOFFICE 和 Qdrant 地址都从 `.env` 或外部环境变量读取。
常见外部依赖变量:
```text
DATABASE_URL
POSTGRES_HOST
POSTGRES_PORT
ONLYOFFICE_ENABLED
ONLYOFFICE_PUBLIC_URL
ONLYOFFICE_BACKEND_URL
QDRANT_URL
```
## 完整启动:本地全栈
适合没有远端数据库和 ONLYOFFICE 的情况。
```bash
docker compose -f docker-compose.full.yml up -d
```
会启动:
```text
main
postgres
qdrant
onlyoffice
```
本地服务端口:
```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` 的清理命令。

View File

@@ -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`、状态和票据引用,不再保存附件原件。

View File

@@ -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 本身。

View File

@@ -0,0 +1,146 @@
# AI 意图规划器
## 功能一句话
把用户一句自然语言先交给大模型识别成“动作和步骤”的结构化计划,再由前端/后端确定性执行器逐步校验和执行;规则只作为模型不可用或结构非法时的兜底。
## 背景
用户真实表达往往不是一个简单关键词,而是一个目标:
```text
去上海出差,辅助国网仿生产服务器部署,交通火车,直接提交
```
这句话包含至少四层语义:
- 业务对象:差旅费用申请。
- 已给字段:地点、事由、交通方式。
- 用户动作:直接提交,不只是生成草稿。
- 执行步骤:生成申请核对表、校验必填字段、查重、提交审批。
如果继续用前端规则直接判断,会越来越像关键词路由;正确方向是让大模型先拆成结构化计划,系统只执行经过白名单校验的步骤。
## 目标
- 大模型作为复杂意图的主识别器,输出结构化 `intent plan`
- `intent plan` 明确描述用户要执行的动作、业务字段、缺失字段和步骤列表。
- 执行器只认计划中的白名单步骤,不直接相信模型文本。
- 字段齐全且用户明确“直接提交”时,系统自动走完整链路。
- 字段缺失、风险阻断、重复申请或低置信度时,系统停在核对表或风险提示。
- 本地规则只作为 `rule_fallback`,不得伪装成模型判断。
## 非目标
- 不让大模型直接写数据库、提交审批或绑定附件。
- 不引入 LangChain 高层 Agent 来替代现有业务服务。
- 不让 LangGraph 直接接管模型供应商密钥、数据库写入或审批副作用。
- 不绕过已有申请预览、规则测算、重复申请核查和提交接口。
- 不把所有财务场景一次性改完;第一阶段先覆盖个人工作台 AI 模式里的差旅申请链路。
## 结构化计划契约
前端执行器消费统一结构:
```json
{
"source": "llm_function_call",
"intent": "create_travel_application",
"requestedAction": "submit",
"confidence": 0.91,
"sourceText": "去上海出差,辅助国网仿生产服务器部署,交通火车,直接提交",
"slots": {
"location": "上海",
"reason": "辅助国网仿生产服务器部署",
"transportMode": "火车"
},
"missingFields": [],
"steps": [
"build_application_preview",
"validate_required_fields",
"run_duplicate_precheck",
"submit_application"
]
}
```
## 步骤白名单
- `build_application_preview`:生成申请核对表,复用现有申请预览能力。
- `validate_required_fields`:用确定性代码校验必填字段和字段格式。
- `run_duplicate_precheck`:提交前查询同日期或重叠申请单。
- `submit_application`:调用现有申请提交动作;只有用户明确要求提交且前置校验通过才执行。
- `save_application_draft`:调用现有申请草稿保存能力。
- `create_reimbursement_draft`:调用现有报销草稿创建能力。
- `associate_attachments`:调用现有附件关联 runner缺少 `receipt_ids` 或匹配不唯一时阻断,不能假成功。
- `link_existing_application`:调用现有报销草稿创建链路并写入申请关联 flag申请单不存在、未审批或已被报销时阻断。
## 数据流
```text
用户输入
steward LLM function calling
模型计划归一化与白名单校验
可执行 intent plan
申请/报销/附件等确定性执行器
核对表、风险提示、草稿或提交结果
```
当模型不可用、超时或返回不可执行结构时:
```text
模型失败
本地 rule_fallback 生成保守计划
同一个执行器继续处理
```
## 安全边界
- 模型只负责“理解”和“拆步骤”,不拥有副作用权限。
- 执行器必须校验 `intent``steps` 是否在白名单内;未知 action 必须阻断,不能调用业务服务。
- 提交前必须走 `readyToSubmit` 和重复申请 precheck。
- 提交类副作用必须带用户确认和 precheck 通过证据。
- 地点、日期、事由等字段仍由现有申请预览和规则校验模块判断。
- 模型置信度低、流程冲突或必填字段缺失时,不自动提交。
## 第一阶段落地
- 新增前端 planner model把后端 `StewardPlanResponse` 映射成前端 `intent plan`
- 个人工作台 AI 模式在命中差旅申请候选时,优先调用现有 `/steward/plans` 获取模型计划。
- 模型计划可执行时,按 `steps` 生成申请核对表并在条件满足时自动提交。
- 模型不可用或计划不可执行时,使用本地 `rule_fallback` 保持体验不倒退。
- 保留既有普通小财管家路径,用来处理无法直接执行或更复杂的多任务场景。
## LangGraph 迁移方向
后端小财管家的规划入口、槽位决策入口和运行时动作决策入口已经开始迁入 LangGraph。
后续不再继续把感知、规划、补字段、运行时动作判断堆进自研 `if/else` 编排,而是按 node 拆分:
- `model_intent_node`:模型 function calling 规划。
- `slot_tool_decision_node`:字段缺口与追问判断,失败时进入规则兜底。
- `runtime_memory_context_node`:合并前端运行时状态和持久化 `steward_state`
- `runtime_tool_decision_node`:用户补充、确认、取消、继续执行等运行时判断。
- `runtime_action_decision_node`:已选流程确认等不需要模型的确定性行动路由。
- `action_plan_node`:生成白名单 `StewardActionStep`,当前已覆盖申请预览、保存草稿、直接提交和报销草稿。
- `human_review_node`:保存草稿、提交审批、关联申请单前的确认点。
- `action_execute_node`:调用现有确定性业务服务;当前已由 `/steward/actions/execute``StewardGraphActionRuntime``StewardActionExecutor` 提供 checkpoint、pending interrupt 和幂等重放。
- `persist_state_node`:合并 conversation state 和 steward state。
当前 `plans` 已返回服务端 `action_steps``slot-decisions` / `runtime-decisions` 已默认走 `StewardGraphRuntime`
如果 LangGraph 图节点异常,服务端会退回原有 Agent 和规则兜底,避免框架层故障影响用户会话。
详细迁移计划见 `LANGGRAPH_RUNTIME_MIGRATION.md`
## 测试策略
- planner model 单元测试模型计划归一化、rule fallback、政策咨询排除。
- 前端静态接线测试:主流程必须先请求 steward plan再 fallback。
- 申请预览回归测试:`直接提交` 不得污染事由;缺日期不得伪造字段。
- 构建验证:`npm --prefix web run build`

View File

@@ -0,0 +1,420 @@
# 小财管家 LangGraph Runtime 迁移计划
> **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:** 把小财管家的感知、规划、记忆、确认和行动逐步迁到 LangGraph runtime减少自研流程编排里的隐藏状态和分支 bug。
**Architecture:** 保留现有 FastAPI API、Pydantic schema、`RuntimeChatService` 模型供应商抽象和业务服务LangGraph 只接管 agent 编排。副作用动作必须通过白名单 action node 调用现有业务服务,保存草稿、提交和关联单据都不能由模型文本直接触发。
**Tech Stack:** Python 3.11、FastAPI、Pydantic、LangGraph 1.x、SQLAlchemy、现有 `AgentConversationService` / `StewardFlowStateService`
---
## 当前状态
已经完成第一阶段接入:
- `server/src/app/services/steward_graph_planner.py`
- 已新增 `StewardGraphPlannerService`
- 已用 LangGraph `StateGraph` 编排 `prepare_context -> detect_model_intent -> done/off_topic/rule_fallback`
- `server/src/app/services/steward_graph_runtime.py`
- 已新增 `StewardGraphRuntime`
- 槽位决策已编排为 `slot_prepare_context -> slot_tool_decision -> done/rule_fallback`
- 运行时决策已编排为 `runtime_memory_context -> runtime_action_decision/runtime_tool_decision -> done/rule_fallback`
- 图内失败会降级到原有规则兜底;端点层图运行异常会回退旧 Agent。
- `server/src/app/services/steward_action_contracts.py`
- 已新增 `StewardActionPlanBuilder`
- 已把申请、报销、保存草稿、直接提交规划成 `StewardActionStep` 白名单动作。
- `StewardGraphPlannerService` 已增加 `attach_action_steps` 节点,计划返回前统一补全动作列表。
- `server/src/app/services/steward_action_executor.py`
- 已新增 `StewardActionExecutor`
- 已接入 `/steward/actions/execute`,支持未知动作拒绝、缺字段阻断、提交确认门禁、重复申请 precheck、申请/报销草稿真实执行、关联申请单和附件关联。
- 申请动作复用 `UserAgentService` 的申请保存/提交能力,报销草稿复用 `ExpenseClaimService.save_or_submit_from_ontology()`
- `server/src/app/services/steward_graph_action_runtime.py`
- 已新增 `StewardGraphActionRuntime`
- 已用 LangGraph `StateGraph` 编排 `action_checkpoint_load -> action_execute_node -> action_checkpoint_persist`
- 已通过 `conversation_id + client_trace_id``AgentConversation.state_json` 中记录 checkpoint、pending interrupt 和幂等重放结果。
- `web/src/services/steward.js` / `web/src/views/scripts/stewardPlanModel.js`
- 前端已新增 `executeStewardAction()` 服务方法。
- steward suggested action 已携带服务端可执行 action step旧申请预览流仍保留兜底。
- `web/src/composables/workbenchAiMode/useWorkbenchAiActionRouter.js`
- AI 工作台点击 `steward_execute_action` 时会调用 `/steward/actions/execute`
- 直接提交会先执行 `run_duplicate_precheck`precheck 通过后再提交申请。
- `server/src/app/api/v1/endpoints/steward.py`
- `/steward/plans``/steward/plans/stream` 默认使用 `StewardGraphPlannerService`
- `/steward/slot-decisions``/steward/runtime-decisions` 默认使用 `StewardGraphRuntime`
- `STEWARD_AGENT_RUNTIME=legacy` 可回退旧 `StewardPlannerService`
- `server/pyproject.toml` / `server/uv.lock`
- 已加入 `langgraph>=1.2.0,<2.0.0`
- `server/tests/test_steward_graph_planner.py`
- 已覆盖默认 LangGraph、显式 LangGraph、legacy 回退、模型成功计划和模型失败后规则兜底。
- `server/tests/test_steward_graph_runtime.py`
- 已覆盖槽位工具节点、图内规则兜底、运行时记忆合并、确定性行动选择和端点级 legacy 兜底。
- `server/tests/test_application_fact_resolver.py`
- 已覆盖 `交通火车` 不污染申请事由,以及 `高铁往返` 这类业务描述仍保留。
当前已完成规划入口、slot decision、runtime decision、基础 action contract、第一版 action executor、前端点击执行闭环以及 action runtime 的 checkpoint / interrupt / 幂等重放。
真实外部模型连通性仍需要用当前 MiniMax / backup 配置再回放确认。
## 官方能力边界
LangGraph 官方文档强调它是低层 agent orchestration runtime重点能力是 durable execution、streaming、human-in-the-loop 和 persistence。
本项目只采用这些底层编排能力:
- Graph API用 state、node、edge 表达流程node 可以是 LLM 调用,也可以是普通业务代码。
- Persistence用 checkpointer 保存 thread scoped state用 store 保存跨线程长期信息。
- Interrupt在需要用户确认时暂停 graph等待外部输入后恢复。
参考:
- <https://docs.langchain.com/oss/python/langgraph/overview>
- <https://docs.langchain.com/oss/python/langgraph/graph-api>
- <https://docs.langchain.com/oss/python/langgraph/persistence>
- <https://docs.langchain.com/oss/python/langgraph/interrupts>
## 总体目标架构
```text
用户输入 / 前端上下文 / 附件
StewardGraphRuntime
├── prepare_context_node
├── model_intent_node
├── slot_decision_node
├── flow_guard_node
├── action_plan_node
├── human_review_node
├── action_execute_node
├── persist_state_node
└── response_adapter_node
现有前端协议StewardPlanResponse / StewardRuntimeDecisionResponse / StewardSlotDecisionResponse
```
关键原则:
- API 响应协议稳定,前端不因为 runtime 替换被迫大改。
- 模型只负责理解、拆步骤、给候选 action不直接写库。
- 所有副作用节点必须调用现有业务服务,并保留确认和审计。
- `RuntimeChatService` 继续是唯一模型调用入口LangGraph 不直接管理供应商密钥。
- `StewardFlowStateService``AgentConversationService` 是 checkpoint 落库的第一候选,不另起一套状态源。
## Runtime State 草案
第一版 graph state 用 `TypedDict`,后续需要持久化时再评估 Pydantic schema。
```python
class StewardGraphState(TypedDict, total=False):
request: StewardPlanRequest
conversation_id: str
thread_id: str
message: str
base_date: date
steward_state: dict[str, Any]
model_call_traces: list[dict[str, Any]]
intent_result: StewardIntentAgentResult | None
slot_decision: StewardSlotDecisionResponse | None
runtime_decision: StewardRuntimeDecisionResponse | None
action_plan: list[StewardActionStep]
pending_interrupt: dict[str, Any]
action_result: dict[str, Any]
response: StewardPlanResponse | StewardRuntimeDecisionResponse | StewardSlotDecisionResponse
fallback_reason: str
```
`thread_id` 推荐使用现有 `conversation_id`,这样 LangGraph checkpoint、业务会话和前端会话能对齐。
## 节点边界
### 感知节点
- `prepare_context_node`
- 输入:`StewardPlanRequest` 或 runtime decision request。
- 输出清洗后的消息、base date、当前 conversation state。
- 禁止:模型调用、数据库写入。
- `model_intent_node`
- 调用:`StewardIntentAgent.detect()`
- 约束:保留 `timeout_seconds=10``max_attempts=3``use_failure_cooldown=False`
- 失败:只写入 `fallback_reason``model_call_traces`,不吞掉错误上下文。
### 决策节点
- `slot_decision_node`
- 迁移对象:`StewardSlotDecisionAgent.decide()`
- 目标:让字段缺口判断进入同一个 graph state不再由前端和后端多个入口各自推断。
- `runtime_decision_node`
- 迁移对象:`StewardRuntimeDecisionAgent.decide()`
- 目标:用户补字段、确认流程、取消当前动作、继续下一任务都走 graph routing。
- `flow_guard_node`
- 负责申请/报销歧义、重复单据、时间重叠、低置信度、off-topic。
- 低置信度或强冲突必须进入确认或阻断,不允许继续 action 执行。
### 行动节点
- `action_plan_node`
- 输出白名单步骤,例如 `build_application_preview``save_application_draft``submit_application`
- 禁止输出自由文本 action。
- `human_review_node`
- 对保存草稿、提交审批、关联申请单等副作用动作生成 interrupt payload。
- 第一阶段可继续返回前端确认动作;第二阶段再接 LangGraph `interrupt()`
- `action_execute_node`
- 只能调用现有业务服务。
- 必须幂等,或在执行前检查动作是否已完成。
- 提交类动作必须检查 `confirmation_required` 和用户确认来源。
- `persist_state_node`
- 合并 `StewardFlowStateService.merge_plan()` / `merge_state()`
- 后续接入 LangGraph checkpointer 后,这里负责业务状态与 checkpoint 的一致性。
### 响应节点
- `response_adapter_node`
- 输出现有 Pydantic response。
- 必须保留 `model_call_traces``planning_source``steward_state`
## 分阶段计划
### Phase 0规划入口接入 LangGraph
状态:已完成。
完成标准:
- [x] 默认 runtime 为 `langgraph`
- [x] `STEWARD_AGENT_RUNTIME=legacy` 可回退。
- [x] `/steward/plans` 真实接口仍返回兼容的 `StewardPlanResponse`
- [x] 模型失败后仍按 10s * 3 次再规则降级。
### Phase 1修通模型成功路径
目标:让真实保存草稿话术至少能走一次 `llm_function_call` 成功路径。
文件:
- 修改:`server/src/app/services/runtime_chat.py`
- 修改:`server/src/app/services/settings.py`
- 修改:`server/tests/test_runtime_chat_service.py`
- 测试:`server/tests/test_steward_intent_agent.py`
步骤:
- [x] 写失败测试:模拟 main provider 超时但 backup provider 可用时,`complete_with_tool_call()` 返回 backup 的 tool call。
- [x] 确认 backup function calling 会在 main 失败后返回 tool call不等待 main 重试结束。
- [ ] 写失败测试MiniMax 返回非 OpenAI tool call 兼容格式时,服务端能记录清晰 trace 并降级。
- [x] 容器内运行:
```bash
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_runtime_chat_service.py \
server/tests/test_steward_intent_agent.py
```
验收:
- [ ] 真实 `/api/v1/steward/plans` 对保存草稿原句返回 `planning_source=llm_function_call`
- [x] `model_call_traces` 能区分 main 失败、backup 成功或全失败。
### Phase 2slot/runtime decision 迁入 graph
目标:把字段缺口和运行时动作判断从独立 agent 类迁进同一个 graph runtime。
状态:已完成。
文件:
- 已创建:`server/src/app/services/steward_graph_runtime.py`
- 已修改:`server/src/app/api/v1/endpoints/steward.py`
- 已测试:`server/tests/test_steward_graph_runtime.py`
- 保留测试:`server/tests/test_steward_slot_decision_agent.py`
- 保留测试:`server/tests/test_steward_runtime_decision_agent.py`
步骤:
- [x] 写失败测试:`StewardGraphRuntime.decide_slot()` 调用 slot node 后返回与现有 `StewardSlotDecisionAgent.decide()` 等价的 response。
- [x] 写失败测试:`StewardGraphRuntime.decide_runtime()` 在用户补充字段时合并 `steward_state`
- [x]`StewardSlotDecisionAgent` 包成 `slot_tool_decision` 节点。
- [x]`StewardRuntimeDecisionAgent` 包成 `runtime_tool_decision` 节点。
- [x] 把已选流程确认和当前运行时记忆归一化拆成 `runtime_memory_context` / `runtime_action_decision` 节点。
- [x] endpoint 在 `STEWARD_AGENT_RUNTIME=langgraph` 时使用 graph runtime。
- [x] endpoint 在 graph runtime 异常时回退旧 Agent避免框架层失效影响会话。
验收:
- [x] `slot-decisions``runtime-decisions``plans` 三个入口都由 LangGraph runtime 路由。
- [x] legacy 回退仍可用。
- [x] 图内工具节点失败时仍有可执行规则兜底。
### Phase 3action node 标准化
目标:把保存草稿、提交审批、关联申请单这些副作用动作变成显式 action node。
状态:基础 action contract、第一版 action executor、前端点击执行闭环、LangGraph action node、checkpoint、pending interrupt 和幂等重放已完成;后续只剩 durable checkpointer 抽象与更完整 trace UI。
文件:
- 已创建:`server/src/app/services/steward_action_executor.py`
- 已创建:`server/src/app/services/steward_action_contracts.py`
- 已创建:`server/src/app/services/steward_graph_action_runtime.py`
- 已修改:`server/src/app/api/v1/endpoints/steward.py`
- 已修改:`server/src/app/services/steward_graph_planner.py`
- 已修改:`server/src/app/schemas/steward.py`
- 已修改:`web/src/services/steward.js`
- 已修改:`web/src/views/scripts/stewardPlanModel.js`
- 已修改:`web/src/composables/workbenchAiMode/useWorkbenchAiActionRouter.js`
- 已修改:`web/src/composables/workbenchAiMode/usePersonalWorkbenchAiMode.js`
- 已测试:`server/tests/test_steward_action_executor.py`
- 已测试:`server/tests/test_steward_graph_planner.py`
- 已测试:`server/tests/test_steward_planner.py`
- 已测试:`web/tests/workbench-ai-intent-planner-model.test.mjs`
- 已测试:`web/tests/steward-actions-service.test.mjs`
- 已测试:`web/tests/steward-plan-message-copy.test.mjs`
- 已测试:`web/tests/workbench-ai-action-router.test.mjs`
白名单 action
- `build_application_preview`
- `save_application_draft`
- `submit_application`
- `link_existing_application`
- `create_reimbursement_draft`
- `associate_attachments`
步骤:
- [x] 写失败测试:申请直接提交计划必须输出 `fill_application_fields -> build_application_preview -> validate_required_fields -> run_duplicate_precheck -> submit_application`
- [x] 写失败测试:保存草稿计划必须输出 `save_application_draft`,并在字段缺失时阻断后续副作用。
- [x] 写失败测试:报销计划必须输出 `fill_reimbursement_fields -> build_reimbursement_preview -> validate_required_fields -> create_reimbursement_draft`
- [x]`StewardTask``StewardPlanResponse` 中增加 `action_steps`
- [x] 实现 `StewardActionPlanBuilder`,由服务端确定性生成白名单 action step。
- [x] 在 LangGraph planner 中新增 `attach_action_steps` 节点。
- [x] 前端 planner model 优先消费服务端 `task.action_steps`,旧响应才回退本地推导。
- [x] 写失败测试:未知 action step 被拒绝,不调用任何业务服务。
- [x] 写失败测试:`submit_application` 缺少确认来源时返回 `needs_confirmation`
- [x] 写失败测试:`submit_application` 必须看到 precheck 通过后才允许真实提交。
- [x] 实现 action registry只允许白名单 action。
- [x] 接入现有申请保存草稿、申请提交和报销草稿服务。
- [x] 前端新增 `executeStewardAction()` 并让 suggested action 携带服务端 action step。
- [x] AI 工作台点击 suggested action 时调用 `StewardActionExecutor`;直接提交先串行执行 precheck再把结果交给 submit。
- [x] 接入 `link_existing_application` 和附件关联的真实执行。
- [x] 把 action executor 包成 LangGraph `action_execute_node`,并接入 checkpoint / interrupt 恢复。
验收:
- [x] 模型不能通过自由文本触发数据库写入,服务端只输出白名单 `StewardActionStep`
- [x] 申请提交动作没有确认时返回 `needs_confirmation`,不会写库。
- [x] 申请提交动作没有 precheck 通过证据时被阻断。
- [x] 保存申请草稿和创建报销草稿通过现有业务服务真实入库,测试覆盖。
- [x] 前端点击可执行 steward action 会真实调用 executor并把结果回写到会话消息。
- [x] 基础副作用动作有 `client_trace_id` 幂等键、conversation checkpoint 和 pending interrupt 记录。
### Phase 4checkpoint 与 human-in-the-loop
目标:让 LangGraph 负责暂停、恢复和跨轮状态,而不是只靠前端消息状态。
文件:
- 已创建:`server/src/app/services/steward_graph_action_runtime.py`
- 已复用:`server/src/app/models/agent_conversation.py`
- 已测试:`server/tests/test_steward_action_executor.py`
步骤:
- [x] 设计 `conversation_id -> thread_id` 映射,当前直接以 `conversation_id` 作为 graph action thread。
- [x] 实现基于数据库的 checkpoint先复用 `AgentConversation.state_json`
- [x] 在提交审批缺确认时生成 pending interrupt payload。
- [x] 用户确认后用同一 `client_trace_id` / `conversation_id` 继续执行;终态请求会幂等重放。
验收:
- [x] 刷新页面后,后端仍保留当前等待确认的 action checkpoint。
- [x] 同一个 action 不会因为重复点击或重试重复写库。
- [ ] 前端完整恢复 UI 仍需继续把 checkpoint 状态展示为可恢复按钮。
### Phase 5可观测性与 legacy 收敛
目标:让 agent 规划过程可解释、可测试、可回放,并逐步删除重复手写编排。
文件:
- 修改:`server/src/app/services/agent_traces.py`
- 修改:`server/src/app/models/agent_run.py`
- 修改:`document/development/Agent链路追踪中心/CONCEPT.md`
- 测试:`server/tests/test_agent_traces.py`
步骤:
- [ ] 每个 graph node 写入 trace event输入摘要、输出摘要、耗时、错误。
- [ ] 前端显示“模型规划中 / 字段判断中 / 等待确认 / 执行动作”阶段。
- [ ] 对 legacy planner 增加废弃标记和删除条件。
- [ ] 删除重复的手写分支前,先保留至少一轮灰度开关。
验收:
- 任何一次用户请求都能从 trace 看出走过哪些 node。
- 删除 legacy 前LangGraph 路径覆盖当前核心申请/报销链路测试。
## 回退策略
- 环境变量:
```bash
STEWARD_AGENT_RUNTIME=legacy
```
- 回退后:
- `/steward/plans` 使用旧 `StewardPlannerService`
- 新 action node 和 checkpoint 不应影响 legacy。
- 回退原因必须写入当天工作日志。
## 验证矩阵
每个阶段至少跑:
```bash
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_graph_planner.py \
server/tests/test_steward_planner.py \
server/tests/test_steward_intent_agent.py \
server/tests/test_runtime_chat_service.py
```
涉及前端执行器时补跑:
```bash
node --test web/tests/workbench-ai-intent-planner-model.test.mjs \
web/tests/workbench-ai-application-gate-model.test.mjs \
web/tests/steward-actions-service.test.mjs \
web/tests/steward-plan-message-copy.test.mjs
npm --prefix web run build
```
每次完成后执行:
```bash
git diff --check
docker exec -w /app -e SERVER_VENV_DIR=/tmp/x-financial-server-venv x-financial-local-linux \
/tmp/x-financial-server-venv/bin/python -m pip check
```
## 不做的事
- 不引入 LangChain 高层 Agent 来替代现有业务服务。
- 不让模型直接调用数据库写入、提交审批或绑定附件。
- 不为了接框架重写申请、报销、OCR、票据夹、审批规则。
- 不在没有测试覆盖时删除 legacy planner。
## 风险与处理
- 模型连通性不稳定:先修 provider 和 backup再扩大 graph 节点。
- checkpoint 与业务 state 双写不一致:先复用 `AgentConversation.state_json`,不要新增第二个业务状态源。
- action node 副作用重复执行:每个 action 必须有幂等键和执行前状态检查。
- LangGraph 传递依赖影响 WebSocket当前 `pip check` 通过;后续若流式接口异常,先核对 `websockets` 版本。
- 节点内继续堆大块逻辑:每个 node 只做一类事,超过 300 行先拆文件。

View File

@@ -0,0 +1,32 @@
# AI 意图规划器 TODO
## 第一阶段
- [x] 落地 AI 意图规划器概念文档,明确大模型负责拆计划、业务代码负责执行。
- [x] 新增前端 planner model统一模型计划和本地 fallback 的输出结构。
- [x] 个人工作台 AI 模式先请求 steward 模型计划,再使用本地 fallback。
- [x] 差旅申请直提链路改为消费 `intent plan.steps`,不再直接消费正则识别结果。
- [x] 补齐 planner model 与主流程接线回归测试。
- [x] 跑前端构建和定向测试。
- [x] 后端 `/steward/plans` 增加 `requested_action` 输出,减少前端从原句推断“提交/保存”的比例。
- [x] 引入 LangGraph并默认用 `StewardGraphPlannerService` 接管 `/steward/plans` 规划编排。
- [x] 落地 LangGraph runtime 迁移计划文档:`LANGGRAPH_RUNTIME_MIGRATION.md`
- [x] 新增 `StewardGraphRuntime`,接管 `slot-decisions``runtime-decisions` 的 LangGraph 路由。
- [x] 为 LangGraph runtime 增加图内规则兜底和端点级 legacy Agent 兜底。
- [x]`/steward/plans` 增加服务端白名单 `action_steps`,覆盖申请、报销、保存草稿和直接提交的基础动作规划。
- [x] 前端 planner model 优先消费服务端 `task.action_steps`,旧响应才回退本地步骤推导。
- [x] 新增 `/steward/actions/execute``StewardActionExecutor`,把未知 action 拒绝、提交确认门禁、precheck 阻断、申请草稿保存、申请提交和报销草稿创建接到现有业务服务。
- [x] 前端新增 `executeStewardAction()` 服务方法,并让 steward suggested action 携带服务端可执行 action step。
- [x] AI 工作台点击可执行 steward action 时调用 `/steward/actions/execute`;申请直接提交会先跑 `run_duplicate_precheck`,通过后再提交。
- [x] `complete_with_tool_call()` 增加 main 失败后 backup tool-call 成功路径测试,保证模型成功路径能从 backup 返回 `llm_function_call`
- [x] 新增 `StewardGraphActionRuntime`,把 action executor 包成 LangGraph `action_execute_node`,并用 `conversation_id + client_trace_id``AgentConversation.state_json` 中持久化 checkpoint。
- [x] 对提交确认生成 pending interrupt重复 client trace 直接重放 checkpoint避免重复保存草稿或重复提交。
- [x] `link_existing_application` 接入现有报销草稿创建链路并写入申请关联 flag。
- [x] `associate_attachments` 接入现有附件关联 runner按 receipt_ids 归集到可匹配报销草稿。
## 后续阶段
- [ ] 用真实 MiniMax / backup 配置再回放 `/steward/plans`,确认真实环境返回 `planning_source=llm_function_call`,不是规则兜底。
- [ ] 将当前 `AgentConversation.state_json` checkpoint 抽象为可替换的 durable checkpointer并补恢复/清理策略。
- [ ] 为每个 LangGraph node 写入可追溯 trace展示模型调用、规则降级、等待确认和动作执行结果。
- [ ] 在 LangGraph 路径覆盖申请/报销核心链路后,再逐步删除旧 `StewardPlannerService` 的重复编排。

View File

@@ -0,0 +1,144 @@
# 附件自动关联后台任务方案
## 背景
小财管家 AI 模式里,用户上传票据后会先做 OCR。当前附件自动关联报销草稿的后半段仍依赖前端会话内存
- 前端保留浏览器里的 `File` 对象。
- 前端在当前会话里完成草稿匹配。
- 用户点击“确认自动关联”后,再用这些 `File` 上传到报销单明细。
这会带来一个核心问题:用户发送消息后,如果刷新页面、切换会话或退出当前会话,浏览器内存里的附件原件会丢失,历史消息中的关联动作就无法继续执行。
票据夹已经能持久化 OCR 结果和源文件,并且 OCR 返回里已经带有 `receipt_id`。所以正式方案应该把“附件关联”从前端内存任务调整为后端可查询任务。
## 目标
1. 用户一上传附件即触发 OCR附件卡片显示“识别中”识别完成后显示“当前附件已识别”。
2. 用户点击发送后,前端不再依赖 `File` 原件做后续归集,而是把 OCR 产生的 `receipt_id` 提交给后端。
3. 后端创建附件关联任务,并在后台继续匹配、复制票据夹源文件、关联报销单明细。
4. 用户退出或刷新当前会话后,前端可通过保存下来的 `job_id` 查询任务状态并更新消息。
5. 如果匹配失败、置信度不足或找不到可编辑草稿,系统给出清晰提示,引导用户补充说明、上传附件或新建草稿。
## 非目标
第一版不承诺后端服务进程重启后任务状态仍可恢复。
本次实现使用轻量内存任务池,解决“用户离开前端会话导致连接断开”的问题。后续如果要做到服务重启、横向扩容、任务审计都可恢复,再新增数据库任务表。
## 核心流程
```text
用户上传票据
→ 前端立即调用 OCR
→ OCR 写入票据夹并返回 receipt_id
→ 附件卡片显示“当前附件已识别”
→ 用户点击发送
→ 前端 POST 创建附件关联任务
→ 后端后台任务继续运行
→ 前端轮询 job_id
→ 成功:消息显示已关联,并刷新报销单详情
→ 失败:消息显示失败原因和下一步动作
```
## 后端设计
### API
新增接口:
- `POST /api/v1/reimbursements/attachment-association-jobs`
- `GET /api/v1/reimbursements/attachment-association-jobs/{job_id}`
创建任务请求只传持久化引用,不传浏览器文件:
```json
{
"receipt_ids": ["receipt-1", "receipt-2"],
"prompt": "请帮我处理已上传的附件。",
"conversation_id": "inline-xxx"
}
```
状态返回:
```json
{
"job_id": "job-xxx",
"status": "running",
"message": "正在匹配可关联的报销草稿...",
"receipt_ids": ["receipt-1", "receipt-2"],
"claim_id": "",
"claim_no": "",
"uploaded_count": 0,
"skipped_count": 0,
"error": ""
}
```
状态枚举:
- `queued`:任务已创建,等待后台执行。
- `running`:正在匹配和归集。
- `succeeded`:已完成自动归集。
- `failed`:无法自动完成,返回可读错误。
### 任务执行
后端任务执行步骤:
1.`receipt_ids` 读取票据夹明细和源文件。
2. 从票据 OCR 文本、结构化字段、日期、城市提取匹配信号。
3. 查询当前用户可见且可编辑的报销草稿,排除申请单和已归档单据。
4. 对候选草稿按日期、城市、事由、草稿状态评分。
5. 置信度足够时选择目标草稿。
6. 为每份票据找到可用费用明细,没有合适明细时创建空明细。
7. 从票据夹源文件复制到报销单附件目录,并沿用 `source_receipt_id` 回填 OCR 信息。
8. 更新票据夹状态为 `linked`
9. 写入任务最终状态。
## 前端设计
### 会话消息
AI 消息需要持久化 `attachmentAssociationJob`
```json
{
"jobId": "job-xxx",
"status": "running",
"receiptIds": ["receipt-1", "receipt-2"]
}
```
这样历史会话恢复后,不再需要浏览器里的 `File` 对象,只要消息里仍有 `jobId`,就可以继续查询后端状态。
### 发送行为
当用户上传附件并点击发送:
1. 前端确认 OCR 已完成。
2. 从 OCR 结果里提取 `receipt_id`
3. 创建后端任务。
4. 立即持久化带 `job_id` 的 pending 消息。
5. 页面仍打开时轮询任务状态。
6. 页面恢复或打开历史会话时,对未完成任务继续轮询。
## 异常处理
- 未完成 OCR禁止发送提示“附件 OCR 识别中,请稍等,识别完成后再继续对话。”
- OCR 失败:禁止发送,提示“请先移除识别失败的附件或重新上传。”
- 没有 `receipt_id`:提示“当前附件没有持久化票据记录,请重新上传后再试。”
- 没有可编辑草稿:任务失败,提示用户先新建或选择草稿。
- 多个候选置信度接近:第一版不自动归集,提示补充说明或手动选择。
- 服务重启导致内存任务丢失:返回任务不存在,前端提示“后台任务状态已失效,请重新发送附件关联请求。”
## 验收标准
1. 上传附件后必须先进入 OCR 识别中状态,识别完成前不能发送对话。
2. 发送附件关联请求后,前端能收到并保存 `job_id`
3. 用户离开当前会话后,后端任务仍会继续执行。
4. 用户回到历史会话后,前端可以根据 `job_id` 查询并更新最终状态。
5. 成功后报销单明细出现附件,票据夹状态变为已关联。
6. 找不到草稿、低置信度、源文件缺失时,消息能给出明确原因。

View File

@@ -0,0 +1,22 @@
# 附件自动关联后台任务 TODO
- [x] 梳理当前前端内存任务断链原因。
- [x] 明确第一版边界:页面/会话退出可恢复,服务进程重启暂不承诺恢复。
- [x] 新增后端任务 schema。
- [x] 新增后端后台任务服务。
- [x] 新增任务创建和查询接口。
- [x] 补后端任务测试。
- [x] 新增前端任务 service。
- [x] 调整 AI 模式附件关联流程为创建后端任务。
- [x] 持久化消息里的 `attachmentAssociationJob`
- [x] 历史会话恢复时继续轮询未完成任务。
- [x] 更新前端源码断言测试。
- [x] 容器内运行后端定向测试。
- [x] 运行前端定向测试和构建。
## 验证记录
- `docker exec -w /app -e SERVER_VENV_DIR=/tmp/x-financial-server-venv x-financial-local-linux /tmp/x-financial-server-venv/bin/pytest -q server/tests/test_attachment_association_jobs.py server/tests/test_ocr_endpoints.py server/tests/test_reimbursement_endpoints.py::test_claim_item_attachment_upload_preview_and_delete server/tests/test_openapi_schema.py`
- `node --test web/tests/workbench-ai-mode-switch.test.mjs web/tests/ai-attachment-association-model.test.mjs`
- `npm --prefix web run build`
- 已重启 `x-financial-local-linux`,并确认 `/api/v1/reimbursements/attachment-association-jobs/not-exist` 返回自定义任务失效提示。

View File

@@ -1,5 +1,11 @@
# 财务规则表补齐开发记录
## 2026-06-05 口径调整
用户明确要求业务招待费超过 500 元、大额办公用品以及金额超过 2000 元的费用申请审批要求进入财务规则中心。因此新增《公司费用申请审批规则》作为申请前置和审批阈值的财务规则依据;风险规则负责引用该财务规则并执行命中判断。
本次调整不恢复旧的单项《业务招待费报销规则》或《办公用品费报销规则》,而是使用统一规则表维护申请审批阈值,避免规则中心再次出现多个口径型规则表。
## 目标
财务规则中心只维护真正具备制度标准、且需要按职级/职务或明确人均标准执行的规则表。没有实际金额分档的费用类型,不在财务规则中心单独生成 Excel 表;其额度控制进入预算中心,申请前置和材料完整性进入风险规则。

View File

@@ -1,5 +1,9 @@
# 风险规则补齐开发记录
## 2026-06-05 口径调整
业务招待费超过 500 元、办公用品超过 2000 元、通用费用超过 2000 元的申请前置要求,制度依据统一改为财务规则《公司费用申请审批规则》。风险规则继续承担执行判断,但 `finance_rule_code` 统一指向 `expense.preapproval.policy`
## 目标
补齐预算、申请前置、报销偏差、费用标准、材料完整性类风险规则,让后续 demo 数据可以形成“预算-申请-报销-风控”的闭环。

View File

@@ -0,0 +1,137 @@
# 报销草稿分支动作收口 概念文档
更新时间2026-06-23
## 功能一句话
当用户发起报销且系统命中可继续的报销草稿时,只提供“查看草稿、继续关联草稿、独立新建报销单”三个明确入口,让用户能看详情、补附件/说明,或确认另起一张新草稿。
## 背景与问题
- 当前现状AI 工作台和报销助手在“我要报销”后会先查询可继续报销草稿;命中草稿时,当前按钮把“继续草稿”和“打开详情”混在一起,并把跳过草稿描述成“关联申请单新建报销单”。
- 用户痛点:按钮语义不清,用户无法判断点击“继续草稿”是打开详情、继续补附件,还是进入申请单关联。
- 业务影响:报销草稿与新建报销单的路径混杂,容易造成重复草稿、附件归集错误或用户误以为已继续处理。
- 为什么现在需要做:截图中的草稿分支是报销入口的第一屏决策,需要先把按钮数量、文案和后续状态固定下来,再继续开发。
## 目标与非目标
### 目标
- [G1] 命中可继续报销草稿时,快捷按钮固定为三个:查看草稿、继续关联草稿、独立新建报销单。
- [G2] “查看草稿”只负责跳转详情页,不承担继续上传或新建逻辑。
- [G3] “继续关联草稿”只负责把当前会话切到“等待上传附件或补充说明”的状态,并锁定目标草稿。
- [G4] “独立新建报销单”先提示用户确认是否新建草稿单据,再进入新草稿创建流程。
### 非目标
- [NG1] 本轮不改后端草稿保存、附件 OCR、风险审核和审批流规则。
- [NG2] 本轮不改变申请单关联命中时的“选择申请单 / 单独新建报销单”逻辑。
- [NG3] 本轮不重做报销助手整体 UI只收口草稿命中态的按钮和动作流。
## 用户与场景
- 目标用户:普通员工在个人工作台 AI 模式或报销助手中发起报销。
- 使用入口:快捷操作“发起报销”、输入“我要报销 / 发起报销 / 新建报销”。
- 核心场景:
1. 系统查到可继续报销草稿,展示草稿卡片和三个按钮。
2. 用户点击“查看草稿”,打开对应单据详情页。
3. 用户点击“继续关联草稿”,系统提示上传相关附件或补充说明。
4. 用户点击“独立新建报销单”,系统询问是否新建草稿单据。
- 异常场景:
- 草稿缺少 `claim_id` 时,“查看草稿”和“继续关联草稿”不能提交无目标动作。
- 历史会话里的旧动作仍可兼容处理,不影响新生成提示。
## 功能能力
- [C1] 输入能力:读取报销草稿候选单据,识别 `claim_id``claim_no`、状态、金额、时间和事由。
- [C2] 处理能力:将草稿命中态动作拆成查看、继续关联、独立新建三条互斥分支。
- [C3] 输出能力:输出固定三按钮,并在点击后生成明确的用户回显和助手提示。
- [C4] 状态与权限:继续关联时记录目标草稿,后续上传附件或补充说明应围绕该草稿继续。
- [C5] 边界与降级:保留旧 `skip_reimbursement_draft_check` 动作兼容,避免历史会话按钮失效。
## 方案设计
### 前端
- 页面/组件:
- `travelReimbursementAssociationGateModel.js` 负责生成草稿命中态文案和三个动作。
- `useWorkbenchAiActionRouter.js` 负责个人工作台 AI 模式的动作分流。
- `useWorkbenchAiExpenseFlow.js` 负责个人工作台中的继续关联提示和独立新建确认提示。
- `useTravelReimbursementSuggestedActions.js` 负责报销助手页面中的相同动作分流。
- 交互状态:
- 查看草稿:沿用 `open_application_detail`,只跳转详情。
- 继续关联草稿:新增动作类型,点击后提示“请上传相关附件,或补充说明”,并记录目标草稿。
- 独立新建报销单:新增动作类型,点击后提示“是否新建草稿单据”,确认后进入现有单独新建流程。
- 展示规则:
- 草稿命中态只显示三个按钮,不再出现“继续草稿”或“不用草稿,关联申请单新建报销单”。
- 按钮文案携带草稿编号,方便用户辨认目标。
- 降级处理:
- 历史旧动作仍走原有分支。
- 缺少草稿 ID 时给出 toast不继续进入无目标关联。
### 后端
- 接口/服务:当前不新增接口。
- 权限与校验:沿用现有详情页与草稿操作权限。
- 持久化:当前不改后端持久化结构。
- 降级处理:后续上传附件或保存草稿仍使用现有 orchestrator 与附件归集能力。
### 算法与规则
- 输入:已筛选出的报销草稿候选。
- 流程:按更新时间排序后取首个草稿作为三按钮默认目标;草稿卡片仍展示候选详情。
- 输出:三个动作对象及后续提示文案。
- 解释:本轮不是匹配算法改造,只是草稿命中后的动作语义收口。
### 数据与契约
- 核心字段:`claim_id``claim_no``original_message``draft_claim_id``selected_claim_no`
- 状态枚举:新增前端动作类型 `continue_reimbursement_draft_association``create_standalone_reimbursement_draft``cancel_standalone_reimbursement_draft`
- 兼容策略:保留 `open_application_detail` 和旧跳过动作分支。
- 版本/审计:通过前端测试锁定动作数量、文案和路由行为。
## 算法与公式
当前功能不涉及显式数学公式。
## 测试方案
后端:
- 当前不新增后端测试;本轮没有修改后端服务或接口。
前端:
- 扩展 `workbench-ai-reimbursement-association-gate.test.mjs`,验证草稿命中态只输出三个动作和新文案。
- 扩展 `workbench-ai-action-router.test.mjs`,验证继续关联、独立新建确认的路由行为。
- 扩展 `travel-reimbursement-guided-flow.test.mjs` 或既有报销助手动作测试,验证旧报销助手使用相同动作语义。
集成:
- 运行相关 Node 测试,确认 AI 工作台和报销助手入口没有回退到旧按钮。
手工验证:
- 在个人工作台 AI 模式输入“我要报销”,命中草稿后检查按钮数量和点击行为。
## 指标与验收
- [A1] 功能验收:草稿命中态固定显示三个按钮,且第一个按钮跳详情,第二个按钮提示上传附件/说明,第三个按钮提示是否新建草稿单据。
- [A2] 性能指标:不增加额外接口查询;仍复用一次单据列表查询。
- [A3] 质量指标:定向前端测试通过,旧动作兼容测试不失效。
- [A4] 安全/权限指标:不绕过详情页和草稿操作原有权限。
- [A5] 可观测性:对话消息中能看到用户选择了哪个分支。
## 风险与开放问题
- 风险:多个草稿同时命中时,三个按钮默认指向最新草稿;用户如需其他草稿,可先通过卡片查看信息后进入详情页处理。
- 风险:全量 `code-size-limits` 当前仍会被既有 `TopBar.vue:824` 阻断;本轮新增和修改的文件均已控制在 800 行内。
- 已处理依赖:复用现有详情页跳转、报销场景选择和草稿保存链路。
- 待确认:后续是否要在草稿卡片内为每张草稿提供独立按钮;本轮先按截图中的三按钮入口收口。
- 降级策略:旧历史会话按钮保持可点击,不强制迁移旧消息。
## 本轮实现记录
- 2026-06-23先落本文档和 TODO再按测试驱动修改前端草稿分支动作。
- 2026-06-23新增 `travelReimbursementDraftBranchModel.js` 承载草稿三分支动作,避免继续放大 `travelReimbursementAssociationGateModel.js`
- 2026-06-23个人工作台和报销助手页面均已接入“继续关联草稿”和“独立新建报销单”的后续提示。

View File

@@ -0,0 +1,73 @@
# 报销草稿分支动作收口 开发 TODO
更新时间2026-06-23
## 使用规则
- 每个 TODO 必须对应 `CONCEPT.md` 中的目标、能力、方案或验收点。
- 只有完成并验证后,才能把 `[ ]` 改成 `[x]`
- 勾选时在任务后补充简短证据,例如文件、接口、命令或验证结果。
- 如果需求发生变化,先更新 `CONCEPT.md`,再调整本 TODO。
## 1. 调研与边界
- [x] [CONCEPT: 背景与问题] 阅读相关页面、动作模型和测试,确认当前“继续草稿”实际走详情打开,“不用草稿”走申请单关联。
证据:`travelReimbursementAssociationGateModel.js``useWorkbenchAiActionRouter.js``useWorkbenchAiExpenseFlow.js``workbench-ai-reimbursement-association-gate.test.mjs`
- [x] [CONCEPT: 目标与非目标] 确认本轮只改草稿命中态按钮与点击流,不改后端草稿保存和风险审核。
证据:`CONCEPT.md` 的目标与非目标章节。
- [x] [CONCEPT: 风险与开放问题] 标记多草稿命中时默认指向最新草稿,历史旧动作保留兼容。
证据:`CONCEPT.md` 的风险与开放问题章节。
## 2. 契约与设计
- [x] [CONCEPT: 功能能力] 定义三个动作分支:查看草稿、继续关联草稿、独立新建报销单。
证据:`CONCEPT.md` 的功能能力和方案设计章节。
- [x] [CONCEPT: 方案设计] 明确个人工作台 AI 模式与报销助手页面共用动作模型,但分别在各自 action router 中处理点击。
证据:`CONCEPT.md` 的前端方案设计章节。
- [x] [CONCEPT: 算法与公式] 明确本轮不涉及显式数学公式。
证据:`CONCEPT.md` 的算法与公式章节。
- [x] [CONCEPT: 指标与验收] 把验收标准拆成按钮数量、按钮文案、点击后提示和旧动作兼容。
证据:`CONCEPT.md` 的指标与验收章节。
## 3. 后端实现
- [x] [CONCEPT: 后端] 本轮不新增后端 schema、service、endpoint、权限和持久化逻辑。
证据:`CONCEPT.md` 明确当前不新增接口。
- [x] [CONCEPT: 数据与契约] 后端响应结构不变,新增内容仅为前端动作类型。
证据:`CONCEPT.md` 的数据与契约章节。
## 4. 算法/规则实现
- [x] [CONCEPT: 算法与规则] 本轮不改候选单据筛选算法,只改命中后的动作分支。
证据:`CONCEPT.md` 的算法与规则章节。
- [x] [CONCEPT: 功能能力] 明确旧动作保留兼容,不删除历史会话能力。
证据:`CONCEPT.md` 的边界与降级说明。
## 5. 前端实现
- [x] [CONCEPT: 前端] 在草稿分支模型中新增动作类型和三按钮生成逻辑。
证据:`travelReimbursementDraftBranchModel.js` 定义 `CONTINUE_REIMBURSEMENT_DRAFT_ACTION``CREATE_STANDALONE_REIMBURSEMENT_DRAFT_ACTION` 和三按钮构造;`travelReimbursementAssociationGateModel.js` re-export 保持兼容。
- [x] [CONCEPT: 前端] 在个人工作台 AI 模式中处理继续关联草稿、独立新建确认和取消新建。
证据:`useWorkbenchAiActionRouter.js` 分流新动作;`useWorkbenchAiExpenseFlow.js` 追加上传附件/说明提示和新建草稿确认提示。
- [x] [CONCEPT: 前端] 在报销助手页面中处理同一批新动作,保持与工作台一致。
证据:`useTravelReimbursementSuggestedActions.js` 接入继续关联、独立新建确认和取消新建;`TravelReimbursementCreateView.js` 传入 `draftClaimId``composerUploadIntent`
- [x] [CONCEPT: 前端] 保留旧动作兼容,不破坏历史会话按钮。
证据:`SKIP_REIMBURSEMENT_DRAFT_CHECK_ACTION``SKIP_REQUIRED_APPLICATION_LINK_ACTION` 仍由原路径处理;相关旧链路测试通过。
## 6. 测试与验证
- [x] [CONCEPT: 测试方案] 先补充失败的前端单元测试,覆盖三按钮生成和路由行为。
证据RED 阶段 `workbench-ai-reimbursement-association-gate.test.mjs``workbench-ai-action-router.test.mjs` 因缺少 `CONTINUE_REIMBURSEMENT_DRAFT_ACTION` 导出失败。
- [x] [CONCEPT: 测试方案] 实现后运行定向前端测试,记录通过结果。
证据:`node --test web/tests/workbench-ai-reimbursement-association-gate.test.mjs``node --test web/tests/workbench-ai-action-router.test.mjs``node --test web/tests/travel-reimbursement-guided-flow.test.mjs``node --test web/tests/travel-reimbursement-review-drawer-switch.test.mjs``node --test web/tests/expense-attachment-draft-selection.test.mjs``node --test web/tests/attachment-association-confirmation.test.mjs` 均通过。
- [x] [CONCEPT: 指标与验收] 回看验收点,确认没有旧文案继续出现在新草稿命中态。
证据:`workbench-ai-reimbursement-association-gate.test.mjs` 断言新草稿命中态有且仅有三个动作,并不再出现“跳过草稿后再关联申请单”。
## 7. 文档收尾
- [x] [CONCEPT: 指标与验收] 实现完成后更新本 TODO 的证据。
证据:本 TODO 已回填实现文件和测试命令。
- [x] [CONCEPT: 风险与开放问题] 根据实现结果更新剩余风险。
证据:`CONCEPT.md` 已记录全量 `code-size-limits` 仍被既有 `TopBar.vue:824` 阻断;本轮文件行数为 `travelReimbursementAssociationGateModel.js:714``travelReimbursementDraftBranchModel.js:143`
- [x] [CONCEPT: 功能一句话] 确认最终实现没有偏离原始目标。
证据:新草稿命中态固定为查看草稿、继续关联草稿、独立新建报销单三个入口。

View File

@@ -0,0 +1,424 @@
# 小财管家
## 功能一句话
小财管家是首页统一财务任务入口,负责把用户的自然语言和附件拆解为多个可确认、可追踪、可分派的申请与报销任务,再调用现有申请助手和报销助手完成执行闭环。
## 背景与问题
当前个人工作台已经提供首页输入框,并能通过本体解析把一句话路由到申请、报销、预算或知识等单一会话。这个能力适合单意图,但用户真实表达经常是多任务组合,例如同时包含出差申请、昨日交通费报销、历史出差费用报销以及多张附件。
现有问题:
- 首页输入框当前会收敛为一个 `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 registryUI 只展示中文业务名称,不展示 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应基于当前计划契约迁移而不是推翻现有申请/报销助手。

View File

@@ -0,0 +1,66 @@
# 小财管家 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 个确认动作。

View File

@@ -0,0 +1,273 @@
# 小财管家本体 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 模板。

View File

@@ -0,0 +1,75 @@
# 小财管家本体 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: 风险与开放问题]

View File

@@ -0,0 +1,88 @@
# 申请单关联归档状态概念文档
## 功能一句话
申请单审批完成后先进入关联单据状态,只有关联的报销单完成付款归档后,申请单才同步归档。
## 背景与问题
当前费用申请单审批完成后,部分列表和进度展示会把申请单视为归档;但业务上申请单只是完成了事前审批,还需要等待后续报销单关联、报销审批、付款完成后,申请单生命周期才真正闭环。
这会导致用户看到报销单仍在处理、申请单却已归档,或者报销单已完成但申请单还停留在进行中的割裂状态。
## 目标
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. 报销单付款完成后,关联申请单同步显示为归档。
## 风险与开放问题
- 旧数据中可能存在已经把申请单审批完成当作归档的数据,本次按新业务规则修正展示与查询口径。
- 如果历史申请单缺少关联报销事件,只能展示“未关联”,不做自动猜测。

View File

@@ -0,0 +1,8 @@
# 申请单关联归档状态开发 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` 均通过。

View File

@@ -0,0 +1,490 @@
# X-Financial 自我进化学习闭环方案
更新日期2026-06-23
## 功能一句话
把 X-Financial 从“规则 + 图谱 + Agent 的智能费控系统”升级为“可持续学习的费控智能体平台”:先通过反馈、回放和影子运行让分析能力变强,再把稳定结论沉淀为可审核、可测试、可回滚的规则、参数和知识资产。
## 背景与问题
当前系统已经具备费用申请、报销、审批、规则中心、知识库、小财管家、数字员工、风险观察、员工画像和财务行为图谱。现有能力已经不是单一算法,而是由多类算法共同形成判断:
- 规则引擎负责执行已上线的制度口径和风险 DSL。
- 风控图谱负责识别重复票据、拆单、高频、跨部门聚集、同组偏离等风险信号。
- 员工画像和申请人画像负责建立同组基线、流程质量和历史行为特征。
- 知识库负责制度检索、引用和政策解释。
- Agent 编排负责意图识别、流程确认、工具调用和可视化交互。
但如果这些模块只各自运行,系统仍然只是“更自动化的费控工具”。要变成自我进化系统,必须解决几个核心问题:
- 每次命中风险后,系统是否知道人工最终确认、驳回、误报还是漏报。
- 新规则、新阈值、新权重上线前,是否能用历史样本回放验证。
- 系统是否能从反复出现的人工反馈中形成候选规则或候选参数。
- 知识库制度变更后,是否能定位受影响的规则、流程和问答。
- Agent 回答错误或流程误判后,是否能沉淀为可复盘样本,而不是只改一处文案。
因此,自我进化不是让大模型直接改生产代码或发布规则,而是建立一条受控学习链路:
```text
运行数据
-> 人工反馈 / 审批结果 / 用户纠错
-> 标签样本与回放集
-> 分析能力校准
-> 候选规则 / 候选参数 / 候选知识修订
-> 影子运行与历史回放
-> 人审发布
-> 上线后持续监控
```
## 核心判断
学习闭环同时让两类能力变强,但顺序不同。
第一层是分析变强。
分析变强指系统越来越知道:
- 哪些风险信号真的值得阻断。
- 哪些风险只适合提醒或补充说明。
- 哪些规则在某些部门、费用类型、职级或场景下误报率高。
- 哪些历史相似单据最终被退回、确认或通过。
- 哪些同组基线更适合当前单据,而不是简单使用全局均值。
这一层优先影响风险分、置信度、推荐动作、抽检策略、自动化模式和解释质量。
第二层是规则变强。
规则变强指当某类分析结论被足够多的反馈和回放证明稳定后,再沉淀为正式规则、规则修订、例外条件或阈值配置。规则变强必须经过测试和审核,不能由模型直接上线。
推荐顺序是:
```text
分析先学习
-> 形成候选结论
-> 回放证明有效
-> 人审沉淀为规则或参数
-> 规则上线后继续接受反馈
```
这样可以避免系统过早把偶然反馈固化为生产规则。
## 目标与非目标
### 目标
- 建立统一的学习闭环总纲串联风险观察、规则反馈、Agent 反馈、审批结果、回放评测和规则中心。
- 明确“分析进化”和“规则进化”的边界。
- 让风险分、置信度、推荐动作、抽检策略和自动化模式具备反馈校准依据。
- 让候选规则、候选参数和候选知识修订进入待审核流程,而不是自动生效。
- 通过历史回放、影子运行和灰度发布降低自我进化风险。
- 为后续实现算法评估中心、反馈样本池和候选规则工厂提供架构依据。
### 非目标
- 不让大模型直接修改生产规则、生产数据或核心代码。
- 不让系统绕过财务、风控或管理员审核自动发布规则。
- 不把员工画像作为惩罚标签;画像只服务于风险解释、排序和复核辅助。
- 不把所有学习结果都固化为规则;低置信反馈只进入分析校准和样本池。
- 不在第一阶段引入复杂机器学习训练平台,优先把样本、标签、回放和门控做稳。
## 现有基础
### 风控图谱
`evaluate_financial_risk_graph()` 已经把单据、员工、部门、费用类型、地点、票据和本体解析合成风险图谱,并输出贡献分、证据、风险等级、置信度、采样策略和决策 trace。
当前贡献分包括:
- `S_rule`:规则信号。
- `S_anomaly`:同组金额异常。
- `S_graph`:图谱异常。
- `S_policy`:制度关联。
- `S_history`:历史反馈。
这说明系统已经具备“分析可解释”和“反馈可进入评分”的基础。
### 规则中心
风险规则已经支持自然语言生成 JSON 风险规则、DSL 校验、风险评分、样例测试、真实场景试运行、测试报告确认、审核发布、反馈记录和修订版本。
这说明系统已经具备“规则可生成、可测试、可审核、可发布”的基础。
### 风险观察反馈池
`RiskObservation``RiskObservationFeedback` 已经能记录风险观察、反馈状态、反馈历史、算法版本和证据数据。
这说明系统已经有学习闭环的核心样本载体。
### 回放评测雏形
`AlgorithmReplayCase``AlgorithmReplaySet` 已经定义了历史单据、本体版本、规则版本、算法版本和反馈标签的回放契约。
这说明系统已经具备把历史风险观察转为评测样本的基础。
### 数字员工
数字员工已有风险图谱巡检、员工画像扫描、反馈样本积累、算法回放评估等任务概念。
这说明后台定时分析能力可以成为学习闭环的执行入口。
## 学习对象分层
### L1. 交互与意图学习
学习对象:
- 用户是否选择了正确流程。
- 小财管家是否误判申请 / 报销。
- 直接提交、保存草稿、附件关联、关联申请单等动作是否被用户纠正。
可进化内容:
- 意图识别提示词。
- 澄清问题策略。
- 流程确认门控。
- 推荐动作排序。
- 前端快捷动作默认项。
禁止直接进化内容:
- 不根据单次对话自动修改规则中心。
- 不把用户一句话纠错直接变成生产规则。
### L2. 风险分析学习
学习对象:
- 风险观察是否被人工确认。
- 命中后是否被退回、补件、通过、忽略。
- 哪些风险信号误报率高。
- 哪些风险在相似场景下总是漏报。
可进化内容:
- 风险分权重。
- 置信度计算。
- 自动化模式门槛。
- 抽检和回放采样策略。
- 同组基线选择策略。
- 风险解释优先级。
禁止直接进化内容:
- 不让系统自动把高风险改成阻断。
- 不让模型直接替代人工确认违规。
### L3. 规则学习
学习对象:
- 多次确认的风险观察。
- 反复出现的漏判反馈。
- 规则测试中的真实场景命中差异。
- 制度变更引起的规则失配。
可进化内容:
- 候选规则。
- 规则修订草稿。
- 例外条件。
- 阈值建议。
- 规则适用范围。
必须经过:
- 样例测试。
- 真实场景试运行。
- 回放评测。
- 人工审核。
- 灰度或发布确认。
### L4. 知识学习
学习对象:
- 知识库问答命中错误。
- 制度引用缺口。
- 规则和制度条款不一致。
- 用户追问中反复出现的政策盲点。
可进化内容:
- 知识文档结构化摘要。
- 制度条款引用。
- 问答检索关键词。
- 规则与制度条款绑定。
禁止直接进化内容:
- 不让模型编造制度。
- 不让模型用未审核知识覆盖正式制度。
## 闭环架构
### 1. 事件采集层
统一采集以下事件:
- 风险观察生成。
- 规则命中和未命中。
- 审批通过、退回、驳回、补件。
- 风控人员反馈确认、误报、漏报、不清楚。
- 用户对 Agent 回答的低分反馈。
- 规则测试、试运行和发布结果。
- 知识库检索命中和无结果。
每个事件都应绑定:
- 业务对象:申请单、报销单、票据、员工、部门、规则。
- 版本信息:算法版本、规则版本、本体版本、知识版本。
- 执行上下文run_id、conversation_id、task_id。
- 处理结果:人工动作、审批状态、反馈标签。
### 2. 标签与样本层
将原始事件转为标准标签:
- `confirmed`:风险被确认。
- `false_positive`:误报。
- `false_negative`:漏报。
- `unclear`:证据不足。
- `over_blocked`:阻断过度。
- `manual_override`:人工覆盖系统建议。
- `policy_changed`:制度变化导致旧判断失效。
同时形成三类样本池:
- 分析校准样本:用于调整风险分、置信度和推荐动作。
- 回放评测样本:用于新算法、新规则、新权重上线前验证。
- 规则候选样本:用于生成或修订规则草稿。
### 3. 离线评估层
定期对候选算法、候选规则、候选参数运行回放。
关键指标:
- 确认率:命中后被人工确认的比例。
- 误报率:命中后被标记误报的比例。
- 漏报率:人工发现风险但系统未命中的比例。
- 阻断准确率:阻断动作最终被确认合理的比例。
- 人工负担:进入人工复核的单据比例。
- 解释完整率:风险观察是否包含规则、证据、基线和相似案例。
- 数据质量通过率:字段完整性、票据 OCR、申请关联等基础数据质量。
### 4. 候选生成层
系统只能生成候选,不直接生效。
候选类型:
- 候选规则:来自反复确认的风险观察或漏判样本。
- 候选规则修订:来自误报较高的规则反馈。
- 候选参数:来自权重、阈值、置信度、自动化门槛的评估结果。
- 候选知识修订:来自制度引用缺口或知识问答低分反馈。
每个候选必须包含:
- 来源样本。
- 触发原因。
- 预期改善指标。
- 可能副作用。
- 回放结果。
- 推荐发布方式。
### 5. 发布门控层
发布顺序:
```text
候选
-> 样例测试
-> 历史回放
-> 影子运行
-> 人工审核
-> 灰度启用
-> 全量发布
-> 上线后监控
```
影子运行期间,新规则或新参数只记录“如果启用会发生什么”,不影响真实审批。
灰度启用期间,只对指定部门、费用类型或测试公司生效。
全量发布后,必须持续监控误报、漏报、阻断和人工覆盖。
## 数据与版本要求
每条学习样本至少保留:
- `sample_id`
- `sample_type`
- `subject_type`
- `subject_id`
- `business_stage`
- `risk_signal`
- `rule_code`
- `algorithm_version`
- `rule_version`
- `ontology_version`
- `knowledge_version`
- `input_snapshot`
- `decision_trace`
- `expected_label`
- `actual_label`
- `feedback_source`
- `feedback_actor`
- `created_at`
每次算法或规则评估至少保留:
- `evaluation_id`
- `candidate_type`
- `candidate_version`
- `baseline_version`
- `replay_set_id`
- `sample_count`
- `metrics_before`
- `metrics_after`
- `regression_cases`
- `approval_status`
## 推荐实施路径
### 第一阶段:补齐评估地基
目标是让系统知道自己判断得好不好。
- 统一风险观察反馈标签。
- 把审批结果、退回原因、补件动作写入风险样本池。
- 将 Agent 低分反馈归因到意图识别、流程路由、知识问答、规则解释、附件处理等类别。
- 建立回放集生成任务,把风险观察和反馈状态转成评测样本。
- 在数字员工工作记录中展示反馈样本摘要和回放评估摘要。
### 第二阶段:分析进化
目标是先让判断更稳。
- 将风险图谱权重、置信度门槛、自动化模式门槛迁移到版本化配置。
- 基于反馈样本计算规则、风险信号、费用类型、部门维度的误报率和确认率。
- 对高误报规则降低默认动作等级,对高确认规则提高抽检优先级。
- 增强同组基线选择,优先使用部门、职级、费用类型、供应商等可解释维度。
- 引入影子参数评估,只记录差异,不直接影响生产。
### 第三阶段:规则进化
目标是把稳定结论沉淀为规则资产。
- 从 confirmed 和 false_negative 样本中生成候选规则。
- 从 false_positive 样本中生成候选例外条件或适用范围收缩建议。
- 候选规则自动进入规则中心草稿,不自动发布。
- 草稿必须完成样例测试、真实场景试运行和测试报告确认。
- 已上线规则只能通过修订版本替换。
### 第四阶段:知识进化
目标是让制度、规则和问答保持一致。
- 对知识库无结果、低分反馈和制度引用缺口做聚类。
- 自动生成知识修订建议或制度引用补充建议。
- 标记受影响规则和问答范围。
- 由制度管理员确认后再更新知识库或规则引用。
### 第五阶段:灰度自进化
目标是在受控范围内逐步提高自动化程度。
- 为低风险、高置信、高证据完整度场景开放自动提醒或自动补件建议。
- 为高风险场景继续保持人工复核或阻断确认。
- 每个自动化动作都必须可解释、可撤回、可追踪。
- 自动化范围随反馈质量逐步扩大。
## 产品边界
自我进化系统里,人和系统的职责要明确。
系统负责:
- 发现模式。
- 聚合证据。
- 生成候选。
- 运行回放。
- 计算指标。
- 提醒风险。
- 记录全链路证据。
人负责:
- 确认制度口径。
- 审核规则发布。
- 判断复杂业务例外。
- 决定阻断、退回或放行。
- 对候选改进做最终确认。
大模型负责:
- 语义理解。
- 候选规则草稿。
- 解释生成。
- 知识摘要。
- 反馈归因辅助。
大模型不负责:
- 最终违规判定。
- 自动发布规则。
- 自动修改生产代码。
- 绕过审核执行高风险动作。
## 验收指标
第一阶段验收:
- 风险观察反馈、规则反馈、Agent 反馈能统一进入样本池。
- 回放集能按算法版本、规则版本、本体版本构建。
- 风险看板能展示确认率、误报率、反馈样本数和待回放样本数。
第二阶段验收:
- 风险分、置信度和自动化动作的调整都有反馈证据。
- 新参数可以影子运行并产生差异报告。
- 高误报规则能被识别并生成修订建议。
第三阶段验收:
- 系统能从反馈样本生成候选规则或候选修订。
- 候选规则能进入规则中心草稿。
- 候选规则必须通过测试报告确认后才允许发布。
第四阶段验收:
- 知识问答低分反馈能定位到知识缺口。
- 制度变更能提示受影响规则和问答范围。
- 知识修订必须保留来源和审核记录。
## 风险与防护
- 反馈噪音:单次反馈不能直接改规则或参数,必须聚合后进入候选。
- 误报固化:规则上线前必须跑历史回放和影子运行。
- 模型幻觉:所有制度、规则和风险结论必须绑定来源证据。
- 自动化越权:阻断、退回、发布规则等高风险动作必须人工确认。
- 数据漂移:长期监控样本量、字段缺失率、费用分布和 OCR 质量。
- 版本混乱:算法、规则、本体、知识和回放集必须带版本号。
## 与现有文档关系
- `document/development/hermes-risk-graph-algorithm/CONCEPT.md`:负责风险图谱算法和风险观察模型。
- `document/development/risk-rule-explainable-flow/CONCEPT.md`:负责风险规则 DSL、解释、测试和发布流程。
- `document/development/数字员工能力库扩展/CONCEPT.md`:负责数字员工技能边界和后台分析任务。
- 本文档负责把上述能力串成“自我进化学习闭环”的总体路线。
## 结论
X-Financial 的自我进化应先让分析变强,再让规则变强。
分析变强是低风险、高收益的第一步:它通过反馈、回放和影子运行持续校准风险分、置信度、动作建议和解释质量。
规则变强是第二步:只有当分析结论经过足够样本验证后,才沉淀为正式规则、规则修订或参数版本,并通过测试、人审和灰度发布进入生产。
最终目标不是“系统自己随便改规则”,而是形成一条可审计、可回放、可控发布的学习链路,让系统越用越懂企业自己的财务控制口径。

View File

@@ -0,0 +1,131 @@
# 费用申请审批财务规则概念文档
## 功能一句话
在财务规则中心新增《公司费用申请审批规则》,统一维护业务招待、办公用品和通用大额费用的事前申请与审批阈值,并让报销风险规则引用该规则执行。
## 背景与问题
现有系统已经有“业务招待无申请”“办公采购无申请”“大额费用无申请”等风险规则,但制度依据主要以风险规则 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 中,本轮先补执行器解析,不新增外键字段。
- 后续如果要支持不同部门或不同职级阈值,可以在同一张财务规则表中扩展分档行。

View File

@@ -0,0 +1,23 @@
# 费用申请审批财务规则 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`

View File

@@ -0,0 +1,86 @@
# 附件上传风险前置复核
## 功能一句话
报销附件上传并完成 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()` 需要继续刷新附件风险和报销级风险。
- 如果用户没有上传附件直接提交,提交阶段仍需要保留兜底风险复核或阻断提示。
- 未来可进一步把上传后复核做成真正后台任务,但本次先保持同步接口返回最新风险结果。

View File

@@ -0,0 +1,28 @@
# 附件上传风险前置复核 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: 测试方案] 证据:核心上传前置复核、提交复用预审、申请/报销风险回归测试通过。

View File

@@ -0,0 +1,3 @@
user1: caoxiaozhu@xf.com / 123456
User2: xiangwanhong@xf.com / 123456
Admin: admin/ admin

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

View File

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

View File

@@ -1,18 +0,0 @@
# Work Log - 2026-05-06
## 今日工作
### 下午
- **修复了 Windows Git Bash 启动脚本报错问题**
- 问题:虚拟环境指向不存在的 python3
- 解决:添加检测函数,无效则重建
- **创建了 work-log 技能**
- 自动记录工作日志
---
# 待处理
- [ ] 安装 PostgreSQL
- [ ] 创建 x_financial 数据库

View File

@@ -1,36 +0,0 @@
# Work Log - 2026-05-07
## 今日工作
- **提交 c00db75** (11:50)
- feat: add employee management, backend health check, and UI improvements
- 完成了员工管理模块(后端 + 前端)
- 添加了后端健康检查
- 整理了 UI 资源
- **提交 2d56bc2** (13:48)
- feat: enhance employee CRUD with search, filters, and security module
- 增强了员工搜索和筛选功能
- 添加了安全模块security.py
- 添加了单元测试
- **提交 b8ba0ea** (14:32)
- feat: add auth module with login and access control
- 为系统实现了完整的登录认证功能
- 后端使用 FastAPI 搭建了 auth 服务,支持管理员密钥验证
- 前端对接了登录接口,实现了 Token 存储和自动登录逻辑
- 设计并实现了基于角色的访问控制RBAC区分超级管理员和普通员工
- **提交 e8f3d97** (15:18)
- feat: add settings page with navigation and access control updates
- 搭建了系统设置页面,支持管理员配置系统参数
- 优化了侧边栏导航交互,增加了收起/展开的流畅动画
- 将访问控制规则统一收敛到 accessControl.js避免散落各处
- 统一了 useSystemState 和 useNavigation 两个 composable 的职责
---
# 待处理
- [ ] 安装 PostgreSQL
- [ ] 创建 x_financial 数据库

View File

@@ -1,30 +0,0 @@
# Work Log - 2026-05-08
## 今日工作
- **提交 adda87a** (08:56)
- feat: add system settings with model connectivity and encrypted storage
- 为系统设置页面新增了配置管理功能,支持管理员修改 AI 模型连接参数
- 引入加密存储方案secret_box.py对敏感配置如 API Key使用对称加密保护
- 后端新增 settings 端点、repository 层和 service 层,实现配置的增删改查
- 新增 model_connectivity.py 服务,支持测试 AI 模型连接是否正常
- 前端设置页面大幅重构,增加了模型配置表单和连接测试功能
- 新增数据库表 system_setting 和 system_setting_secret 存储配置和加密值
- 编写了 settings 相关的单元测试,确保配置持久化和服务逻辑正确
- **提交 c5486dd** (10:52)
- feat: 启用后端自动启动与 Setup 引导流程增强
- 启用了后端自动启动功能,用户访问前端时后端自动拉起,无需手动启动 server
- 增强了 Setup 引导流程,新增后端启动进度追踪,分 5 步展示config → deps → server → health → done
- 网络绑定从 127.0.0.1 扩展到 0.0.0.0,支持远程浏览器访问部署的系統
- API URL 动态化,通过 localStorage 持久化配置,支持运行时修改
- 新增后端启动探针probe自动检测后端就绪状态后才允许浏览器继续操作
- Setup 表单智能判断浏览器 host将本地地址自动转换为 0.0.0.0 供远程访问
- **提交 8656866** (11:14)
- feat: 重构模型配置存储与 API Key 加密管理
- 新增 SystemModelSetting 模型slot 为 PK支持 main/backup/vlm/embedding 四个模型槽位配置
- 废弃旧的加密存储方案,改用更规范的数据库表存储加密的 API Key
- 兼容旧版 admin secret 格式,将历史密码记录迁移到标准 scrypt 哈希格式
- 前端 API URL 智能解析,当后端配置为回环地址但浏览器非回环时,自动使用浏览器 host 访问
- 改进 API Key 输入体验,聚焦时自动清除遮罩显示,并提示已从数据库加载密钥

View File

@@ -0,0 +1,17 @@
# 2026-05-23 工作日志
## 远端依据
- 分支:`origin/main`
- 提交:`575f093`
## 工作内容
- 新增风险规则生成引擎,支持从知识、规则资产和业务上下文中生成风险规则草案。
- 增强知识图谱可视化能力,补充知识摄取日志图谱节点、证据和运行信息展示。
- 扩展审计页、日志详情页和归档中心交互,便于追踪规则生成、知识索引和报销会话链路。
- 补充风险规则生成、知识摄取日志、报销引导流程等测试覆盖。
## 提交记录
- `575f093` feat: 新增风险规则生成引擎与知识图谱可视化

View File

@@ -0,0 +1,18 @@
# 2026-05-24 工作日志
## 远端依据
- 分支:`origin/main`
- 提交:`50b1c3f`
## 工作内容
- 增强规则资产管理能力,引入规则测试、规则流图和运行时调试相关页面能力。
- 新增 Hermes Agent 相关架构、数据库、风险扫描、报销报告和部署说明文档。
- 扩展 Hermes 配置、报告、风险扫描、调度等后端模型和服务。
- 新增文档中心页面与时间工具,优化审计页、日志页、设置页和导航交互。
- 清理旧风险规则文件并补充知识解析、RAG、规则生成和用户代理服务测试。
## 提交记录
- `50b1c3f` feat: 增强规则资产管理与审计页面运行时调试

View File

@@ -0,0 +1,18 @@
# 2026-05-25 工作日志
## 远端依据
- 分支:`origin/main`
- 提交:`d0e946c`
## 工作内容
- 完善文档中心和报销申请链路,新增首页参考稿并优化侧边栏结构。
- 抽取用户代理申请服务,增强报销单、申请单、本体识别和编排流程。
- 强化文档中心未读状态、侧边栏入口、详情提醒和申请预填能力。
- 优化报销创建页、差旅详情页和小财管家会话状态。
- 补充文档中心、申请提交确认、助手建议动作、侧边栏折叠等测试。
## 提交记录
- `d0e946c` feat: 完善文档中心与报销申请交互及侧边栏重构

View File

@@ -0,0 +1,20 @@
# 2026-05-26 工作日志
## 远端依据
- 分支:`origin/main`
- 提交:`0e861d8``df49103`
## 工作内容
- 增强风险规则生成引擎和预算中心页面,打通预算中心本体、风险规则评分和预算后端服务。
- 新增差旅风险规则库,补充预算实体、预算服务、预算图表和预算中心视图。
- 完善预算中心图表展示、确认对话框和费用管控交互。
- 扩展规则资产、员工、报销申请、审批和预算相关测试,覆盖新引擎与预算链路。
## 提交记录
- `0e861d8` feat: 增强风险规则生成引擎与预算中心页面
- `e1e515e` feat: 新增预算中心本体与风险规则评分回填
- `e7bef08` feat: 新增预算后端服务与差旅风险规则库
- `df49103` feat: 完善预算中心图表与确认对话框交互

View File

@@ -0,0 +1,22 @@
# 2026-05-27 工作日志
## 远端依据
- 分支:`origin/main`
- 提交:`2dcc721``d4d5d40`
## 工作内容
- 重构全局 UI 主题皮肤与样式模块,统一页面视觉基线。
- 优化报销创建页面样式和洞察面板交互,提升申请与报销过程的可读性。
- 新增预算助手报告组件,补强预算报告、报销交互细节和申请关联能力。
- 完善审批退回流程与报销申请关联逻辑。
- 新增预算费控模型和报销审批流引擎,为后续动态审批与预算分析打基础。
## 提交记录
- `2dcc721` style: 全局 UI 主题皮肤重构与样式模块化
- `b1a9c8a` fix: 优化报销创建页面样式与洞察面板交互
- `7d32eae` feat: 新增预算助手报告组件并优化报销交互细节
- `cbb98f4` feat: 完善审批退回流程与报销申请关联
- `d4d5d40` feat: 新增预算费控模型与报销审批流引擎

View File

@@ -0,0 +1,21 @@
# 2026-05-28 工作日志
## 远端依据
- 分支:`origin/main`
- 提交:`04cd6d0``064eeb6`
## 工作内容
- 重构工作台首页,新增数字员工管理页面和员工行为画像算法。
- 引入费用风险标签体系、员工画像标签分页和 ECharts 统一图表能力。
- 在列表详情页面引入共享 shell并逐步拆分审计列表、详情和模型逻辑。
- 备份列表详情 shell 重构前工作区,降低大规模 UI 抽取风险。
## 提交记录
- `04cd6d0` feat: 新增数字员工管理页面与工作台首页重构
- `8a4a777` feat: 新增员工行为画像算法与费用风险标签体系
- `e384318` feat: 引入 ECharts 统一图表并完善员工画像标签分页
- `b383244` chore: backup workspace before list detail shell refactor
- `064eeb6` refactor(ui): introduce shared list detail shells

View File

@@ -0,0 +1,21 @@
# 2026-05-29 工作日志
## 远端依据
- 分支:`origin/main`
- 提交:`99e9079``4c59941`
## 工作内容
- 拆分审计列表详情流程和模型,复用共享列表 shell降低重复 UI 结构。
- 完成共享 shell 与加载状态收口,统一页面加载、空态和列表体验。
- 统一后端分页查询接口与前端服务层适配。
- 新增票据夹模块,优化 OCR、员工画像和票据相关服务。
## 提交记录
- `99e9079` refactor(audit): split list detail flows
- `64cc76c` refactor(audit): reuse list shells and split models
- `e080105` feat(ui): finalize shared shells and loading states
- `678f64d` feat: 统一后端分页查询与前端服务层适配
- `4c59941` feat: 新增票据夹模块并优化 OCR 与员工画像服务

View File

@@ -0,0 +1,18 @@
# 2026-05-30 工作日志
## 远端依据
- 分支:`origin/main`
- 提交:`7989f3a`
## 工作内容
- 新增风险图谱算法能力,围绕风险节点、关系和来源构建设计与实现文档。
- 扩展系统仪表盘和操作反馈体系,提升风险与业务操作的可解释性。
- 增强规则资产、风险规则模拟、员工、申请和审批相关服务。
- 更新前端预算中心、审计、工作台、差旅申请和详情页等模块。
- 补充文档、测试和运行资产,支撑风险图谱与仪表盘演示。
## 提交记录
- `7989f3a` feat: 新增风险图谱算法与系统仪表盘及操作反馈体系

View File

@@ -0,0 +1,18 @@
# 2026-06-01 工作日志
## 远端依据
- 分支:`origin/main`
- 提交:`92444e7`
## 工作内容
- 扩展风险规则体系,增强规则生成、规则执行、复合规则和本体字段治理能力。
- 新增审批动态路由能力,补强申请审批、报销审批和流程转派场景。
- 推进预算中心列表化改造,配套更新预算、审计、文档中心和工作台页面。
- 更新项目协作规范、环境配置和规则表资产。
- 补充风险规则、本体、审批、文档中心和申请流程相关测试。
## 提交记录
- `92444e7` feat: 扩展风险规则体系、审批动态路由与预算中心列表化改造

View File

@@ -0,0 +1,18 @@
# 2026-06-02 工作日志
## 远端依据
- 分支:`origin/main`
- 提交:`ca691f3``0c74b4a`
## 工作内容
- 优化差旅报销预审流程和个人工作台 UI 体系,增强申请、报销和智能助手联动。
- 重构财务看板口径,补充半年模拟数据和报销状态注册表。
- 扩展财务看板、风险看板、员工画像、工作台、差旅详情和申请创建页面。
- 补充模拟数据、状态口径、看板展示和报销流程相关测试。
## 提交记录
- `ca691f3` feat: 优化差旅报销预审流程与个人工作台 UI 体系
- `0c74b4a` feat: 财务看板口径重构与半年模拟数据及报销状态注册表

View File

@@ -0,0 +1,49 @@
# 2026-06-03 工作日志
## 远端依据
- 分支:`origin/main`
- 提交:`faa39e6``87da5df`
## 工作内容
- 建设数字员工财务报告体系、定时提醒和看板快照调度能力。
- 优化风险看板、数字员工看板、预算与风险卡片、共享加载态和仪表盘展示。
- 完善工作台费用进度、费用统计详情弹窗、分布图、画像雷达和个人工作台标签展示。
- 推进本体字段治理和风险规则模板执行器重构。
- 优化申请与报销流程,包括申请关联限制、交通方式显式要求、手工明细入口收口和管理员工作台权限。
- 完善文档中心未读状态、铃铛通知、排序、全部已读和顶部通知持久化。
- 细化风险可见性和审核人风险提示,修复规则标准调整后的风险说明保留问题。
## 提交记录
- `faa39e6` test(dashboard): cover shared loading overlay
- `d060f89` style(dashboard): reuse shared loading overlay
- `0d6327a` feat(dashboard): polish risk and digital employee boards
- `15006a0` feat: 数字员工财务报告体系与定时提醒及看板快照调度
- `27dd2f0` feat(dashboard): reorganize budget and risk cards
- `6fc5e66` feat(workbench): show progress update time first
- `3130c42` feat(workbench): separate stale progress items
- `20cb60e` feat(workbench): add expense stats detail modal
- `31052d0` feat(workbench): keep progress detail return context
- `74d488a` fix(workbench): center progress expense type
- `18d716b` feat(workbench): show expense distribution as donut chart
- `e12b140` fix(workbench): show single expense distribution chart
- `34457f9` feat: 本体字段治理与风险规则模板执行器重构
- `8887cf5` fix(workbench): stretch profile tag card
- `04f0951` fix: restrict application linking for reimbursement drafts
- `59d3bf0` fix(auth): keep admin out of personal workbench
- `9219854` fix: require explicit transport mode for applications
- `513ff90` fix: remove manual expense detail add action
- `4717ee6` fix(documents): refine unread badges and mark all read
- `8c2f301` fix(documents): sort newest rows first
- `c73178b` fix(documents): move unread notice into bell
- `95956af` fix(notifications): refine bell notification center
- `9c24a85` fix(workbench): remount expense stats chart on reopen
- `67b81a1` fix(workbench): replay profile radar animation
- `8e24775` fix: handle risk explanation standard adjustment
- `cb36d78` fix: 优化顶部导航栏布局与工作台摘要展示并清理旧票据数据
- `0f8bc40` fix: preserve reviewer risk notice after standard adjustment
- `b9826a1` fix: keep adjusted risks visible to reviewers
- `75d5c17` feat(workbench): persist topbar notification state
- `87da5df` feat: 风险可见性控制与差旅详情页交互优化

View File

@@ -0,0 +1,18 @@
# 2026-06-04 工作日志
## 远端依据
- 分支:`origin/main`
- 提交:`1cbf3fe``f60ceba`
## 工作内容
- 增强报销预审会话状态管理,优化工作台与报销创建页交互。
- 完善小财管家意图规划、管家计划和报销提交编排。
- 扩展用户代理、编排器、差旅报销会话、提交组合器和申请预填相关能力。
- 补充小财管家计划、报销预审、提交编排和工作台交互测试。
## 提交记录
- `1cbf3fe` feat: 报销预审会话状态管理与工作台交互增强
- `f60ceba` feat: 小财管家意图规划与报销提交编排增强

View File

@@ -0,0 +1,18 @@
# 2026-06-06 工作日志
## 远端依据
- 分支:`origin/main`
- 提交:`e124e4b`
## 工作内容
- 重构报销审批流,贯通小财管家计划、报销预审、申请提交和审批链路。
- 扩展管家计划流式接口、报销单服务、申请草稿、风险提示和上下文工具。
- 优化工作台 AI 模式、差旅报销创建页、申请详情页和文档中心交互。
- 更新 Docker、环境配置和忽略规则配合新流程运行。
- 补充大量后端、前端和端到端测试,覆盖审批流、预算分析、申请确认和工作台入口。
## 提交记录
- `e124e4b` feat: 报销审批流重构与管家计划全链路贯通

View File

@@ -0,0 +1,18 @@
# 2026-06-09 工作日志
## 远端依据
- 分支:`origin/main`
- 提交:`25724c3`
## 工作内容
- 同步报销流程和工作台近期改动,继续完善申请、报销、风险建议与工作台协同。
- 调整 Docker、GPU、Postgres 和环境配置,适配本地与容器化运行。
- 补充移动端相关资源和前端页面改动。
- 更新服务端报销、员工、本体、规则、用户代理和审批链路实现。
- 扩展工作台、申请详情、报销创建、审计和文档中心相关测试。
## 提交记录
- `25724c3` feat: 同步报销流程与工作台改动

View File

@@ -0,0 +1,15 @@
# 2026-06-12 工作日志
## 远端依据
- 分支:`origin/main`
- 提交:`336fee9`
## 工作内容
- 更新仓库忽略规则,将 `.superpowers` 工具缓存目录排除出版本管理。
- 降低本地代理工具缓存误入库风险。
## 提交记录
- `336fee9` chore: 忽略 .superpowers 工具缓存目录

View File

@@ -0,0 +1,17 @@
# 2026-06-13 工作日志
## 远端依据
- 分支:`origin/main`
- 提交:`8b952c9`
## 工作内容
- 拆分差旅报销创建工作流,降低单页脚本和流程模型复杂度。
- 抽取差旅报销草稿、提示动作、附件、会话状态和提交组合等前端逻辑。
- 强化报销创建页的可维护性,为后续 AI 工作台与报销流程复用做准备。
- 补充报销创建、会话状态、建议动作和附件流程测试。
## 提交记录
- `8b952c9` refactor(travel): split reimbursement create workflow

View File

@@ -0,0 +1,19 @@
# 2026-06-15 工作日志
## 远端依据
- 分支:`origin/main`
- 提交:`5747e85``9f7b8b4`
## 工作内容
- 恢复上传阶段规则中心复核,确保附件上传时的风险识别链路有效。
- 修正报销明细和风险建议对齐问题,让风险提示能对应到具体费用行。
- 细化差旅报销小财管家流程,覆盖申请、报销、计划、回显和提交流程。
- 更新规则表、后端服务、前端工作台和差旅报销创建页,补齐流程测试。
## 提交记录
- `5747e85` fix(risk): restore upload-time rule center review
- `7927417` fix(claim): align risk advice with expense rows
- `9f7b8b4` Refine travel reimbursement steward flow

View File

@@ -0,0 +1,26 @@
# 2026-06-17 工作日志
## 远端依据
- 分支:`origin/main`
- 提交:`470f343``b8915a2`
## 工作内容
- 收窄差旅路线风险指标,减少误报并统一风险标记口径。
- 调整 Web 端口配置,补充工具临时目录忽略规则。
- 重构报销审批流,优化差旅详情、风险建议卡片和文档中心交互。
- 更新财务差旅与通信费用规则表。
- 同步报销审批流和预算分析测试。
- 归档用户报销票据附件,清理运行资产边界。
## 提交记录
- `470f343` fix(expense): narrow travel route risk indicators
- `0d525fa` chore: 忽略 .codex-temp 工具临时目录
- `09a66c7` chore: 将 web 端口由 5173 调整为 5273
- `1f4681f` feat(claim): 重构报销审批流并收敛风险标记
- `a3e5295` feat(rules): 更新财务差旅与通信费用规则表
- `0fac8b6` feat(web): 优化差旅详情、风险建议卡片与文档中心交互
- `4199feb` test: 同步报销审批流与预算分析测试
- `b8915a2` chore(storage): 归档用户报销票据附件

View File

@@ -0,0 +1,32 @@
# 2026-06-18 工作日志
## 远端依据
- 分支:`origin/main`
- 提交:`cce19e4``4d04f4e`
## 工作内容
- 完善 Steward 业务无关输入拦截,支持 `off_topic` 计划、前端引导话术和场景细分。
- 增强服务端启动 bootstrap、缓存预热和登录目录并发幂等控制。
- 仅放行财务业务相关问题的信号校验,强化业务边界。
- 更新差旅、通信等财务规则表,并清理用户历史报销票据附件。
- 优化工作台 AI 模式、差旅/风险建议交互、用户消息气泡和引用图标。
- 新增 X-Financial 改进路线图,补充未来改进方向。
- 更新忽略规则,避免 `.env``.nezha``.omo` 等本地或敏感配置入库。
## 提交记录
- `cce19e4` feat(steward): 拦截业务无关输入返回 off_topic 计划
- `4343253` feat(steward): 前端支持 off_topic 与引导话术
- `c28e99b` chore(gitignore): 忽略 .nezha/ 与 .omo/ 本地工具目录
- `38653fa` chore(storage): 清理用户历史报销票据附件
- `35372c6` feat(rules): 更新差旅与通信费用等财务规则表
- `59ba76c` feat(startup): 服务端启动 bootstrap 与缓存预热
- `3f17619` fix(auth): 登录目录就绪幂等化与并发控制
- `127d603` feat(ontology): 仅放行财务业务相关问题的信号校验
- `a6674a1` feat(steward): off_topic 场景细分与引导回复
- `0cde1f8` feat(web): 工作台 AI 模式与差旅/风险建议交互优化
- `a2f67af` docs: 新增 X-Financial 改进路线图
- `3131112` style(web): 调整 AI 模式用户消息气泡布局与引用图标
- `4d04f4e` chore(gitignore): 忽略 .env 防止敏感配置入库

View File

@@ -0,0 +1,27 @@
# 2026-06-20 工作日志
## 远端依据
- 分支:`origin/main`
- 提交:`3d69f85``3b74a33`
## 工作内容
- 细化申请单阶段风险可见性并更新规则表。
- 拆分工作台 AI 模式报销预审和文档查询模型。
- 新增申请核对预览快速建单接口,并统一平台管理员判定。
- 接入 AI 工作台申请预览动作,支持申请单草稿保存和删除权限统一。
- 将单号规则收紧为 `A/R/D + 8 位` 紧凑格式,并适配后端测试。
- 增强 AI 工作台会话、文档卡片渲染和 AI 文档查询卡片。
## 提交记录
- `3d69f85` feat(risk): 申请单阶段风险可见性细化与规则表更新
- `304bbe1` feat(web): 工作台 AI 模式报销预审与文档查询模型拆分
- `729d833` feat(server): 新增申请核对预览快速建单接口与平台管理员判定统一
- `96c2e10` feat(web): 统一平台管理员判定与 AI 工作台申请预览动作接入
- `47c6a4b` refactor(server): 单号规则收紧为 A/R/D+8 位紧凑格式
- `81e990a` feat(server): 申请单支持草稿保存并统一删除权限口径
- `0cda750` feat(web): AI 工作台会话与文档卡片渲染增强
- `8158716` test(server): 适配 A/R/D 紧凑单号格式
- `3b74a33` feat(web): AI 文档查询卡片重构与单号判定统一

View File

@@ -0,0 +1,24 @@
# 2026-06-21 工作日志
## 远端依据
- 分支:`origin/main`
- 提交:`8b34954``08a4fa3`
## 工作内容
- 增强 AI 文档详情引用解析和查询卡片展示。
- 优化差旅申请详情进度 viewer、审批信息、加载态和领导意见事件样式。
- 移除顶栏 AI 快捷操作区,让非首页顶栏更收敛。
- 优化 OCR 处理PDF 文本层可用时跳过 worker 调用,并补装 `poppler-data`
- 将 AI 工作台附件改为卡片化展示,支持单项移除并适配卡片样式与交互。
## 提交记录
- `8b34954` feat(web): AI 文档详情引用解析与查询卡片增强
- `24b5b71` feat(web): 差旅申请详情进度 viewer 与审批/加载态组件增强
- `1986b0d` style(web): 移除顶栏 AI 快捷操作区并优化差旅领导意见事件样式
- `88e91a5` feat(ocr): PDF 文本层可用时跳过 worker 调用并补装 poppler-data
- `669d22e` feat(web): 差旅领导意见事件结构化与申请审批信息增强
- `d660a96` feat(web): AI 工作台附件改为卡片化展示并支持单项移除
- `08a4fa3` style(web): AI 工作台附件卡片样式与交互适配

View File

@@ -0,0 +1,28 @@
# 2026-06-22 工作日志
## 远端依据
- 分支:`origin/main`
- 提交:`6d33ba5``bc743ad`
## 工作内容
- 执行 800 行源码限制重构拆分服务、组件、composable、工具函数和测试辅助模块。
- 拆分 Docker 主应用编排和完整本地栈,补充 Docker 说明、重构计划和 UI 参考图。
- 更新差旅、交通、通信等财务规则表。
- 恢复差旅详情子组件样式。
- 扩展报销单输出工号/邮箱,并支持申请人邮箱前缀匹配。
- 新增报销单关联申请单门控和草稿检测流程。
- 增强申请单预览编辑器和报销流程细节。
## 提交记录
- `6d33ba5` refactor: enforce 800 line source limits
- `607e127` chore(docker): 拆分主应用编排与完整本地栈
- `e42deda` docs: 同步 Docker 编排说明并补充重构计划与 UI 参考
- `103f225` chore(rules): 更新差旅/交通/通信等财务规则表
- `1b04ee1` fix(web): restore travel detail child component styles
- `aa965da` feat(server): 报销单输出工号/邮箱并扩展申请人邮箱前缀匹配
- `ba444a5` feat(web): 报销单新增关联申请单门控与草稿检测流程
- `ded8b39` feat(web): 申请单预览编辑器增强与报销流程细节适配
- `bc743ad` chore(rules): 更新公司通信费报销规则表

View File

@@ -0,0 +1,28 @@
# 2026-06-23 工作日志
## 远端依据
- 分支:`origin/main`
- 提交:`84a8998``73966b3`
## 工作内容
- 新增票据文件夹资产缓存和统一文档预览生成,完善 OCR、票据夹和附件分析链路。
- 前端接入票据夹资产缓存,完善 AI 工作台附件流程、智能入口识别和草稿附件选择。
- 统一 `WEB_PORT` 回退到 `5173`,并让烟雾检查支持开关。
- 更新交通、通信、差旅、出差等财务规则表。
- 重构通知中心列表行布局和时间标签格式。
- 移除误跟踪的 `expense_claims` 运行时票据文件。
- 修复孤儿申请预览消息,触发重新生成可编辑表格。
- 合并财务工作流模块抽取申请事实解析、Steward 计划拆分、可信 HTML、加载状态和文档协议常量。
## 提交记录
- `84a8998` feat(server): 票据文件夹资产缓存与文档预览统一生成
- `e725b7f` feat(web): 票据夹资产缓存接入与 AI 工作台附件流程完善
- `dc4cad2` chore(env): WEB_PORT 统一回退为 5173 并烟雾检查改为可开关
- `0122f3b` chore(rules): 更新交通/通信/差旅/出差等财务规则表
- `8094333` style(web): 通知中心列表行布局重构与时间标签格式化
- `f17098a` chore(repo): 移除误跟踪的 expense_claims 运行时票据文件
- `1f40ce3` fix(web): 孤儿申请预览消息触发重新生成可编辑表格
- `73966b3` refactor: consolidate finance workflow modules

View File

@@ -0,0 +1,556 @@
# 2026-06-24 工作日志
## 当日工作内容
- 09:41我开始为 X-Financial 补一个专门写修改日志的项目级 Skill目标是让后续每次 bugfix、新功能、重构或配置/文档修改之后,都能留下有时间、有操作、有判断的增量记录。
- 修改:新增 `agent-change-log` Skill约定触发场景、每日日志位置、三段式结构、TODO 勾选规则和拟人化记录方式。
- 修改:新增 Skill 展示元数据,方便后续通过 `$agent-change-log` 识别和调用。
- 修改:准备把 `AGENTS.md` 接上强触发规则,避免后续代理只记得“写代码”而忘了“写修改记录”。
- 操作:沿用既有 `document/work-log/YYYY-MM-DD.md` 日志目录,不另起一套新路径,避免历史记录分裂。
- 验证09:42 已补做结构检查,见下一条记录。
- 影响:后续修改会从“最终总结”升级为“过程可追踪”,当天工作内容、遗留问题和 TODO 会随着每轮修改持续增长。
- 09:42我按刚写好的 `agent-change-log` 规则做了第一次自检,并把验证结果回填到当天日志里。
- 操作:运行 `git diff --check` 检查本轮涉及文件的空白问题。
- 操作:用 `rg` 检查 Skill frontmatter、`AGENTS.md` 触发规则,以及日志中的 `当日工作内容``遗留问题``TODO` 三个固定标题。
- 验证:`git diff --check` 无输出,说明补丁没有空白错误;`rg` 能命中 `agent-change-log`、日志路径和三段式标题。
- 影响:这个日志 Skill 已经有了可执行的触发条件、固定记录位置和第一次样例记录,后续修改可以直接照这个格式增量追加。
- 09:43我继续检查了新 Skill 的版本管理可见性,确认它已经落在本机项目目录,但默认会被 `.gitignore` 忽略。
- 操作:运行 `git check-ignore -v` 检查 `.codex/skills/agent-change-log/SKILL.md``agents/openai.yaml` 的忽略来源。
- 验证:命中 `.gitignore:10:.codex/`,说明普通 `git status` 不会显示这个新 Skill`ls` 已确认两个文件真实存在。
- 影响:本机后续可以使用这个 Skill但如果要把它随仓库交付需要后续显式调整忽略规则或强制加入版本管理。
- 09:44我收紧了 `.gitignore` 规则,让新建的 `agent-change-log` Skill 能进入普通版本管理视野,同时继续保护 `.codex` 下其他本地状态。
- 修改:把 `.codex/` 改成 `.codex/*`,再只放行 `.codex/skills/agent-change-log/**`
- 操作:保留 `.codex` 其他内容默认忽略,避免把本地运行缓存、临时状态或其他未审查 Skill 一起暴露出来。
- 验证:`git status --short` 已能看到 `agent-change-log` 的两个新增文件;`git diff --check` 仍然无输出。
- 影响:后续提交这个 Skill 时不需要强制添加,普通 `git status` 就能看到相关文件。
- 10:01我按你的补充要求把“先拉取/检查 Git分析其他智能体提交”纳入 `agent-change-log` 的硬流程。
- Git 提交检查:已运行 `git fetch --all --prune``HEAD..origin/main` 未发现 upstream 新提交。当前本地 `main` 显示 ahead 1后续在 10:02 单独补充分析本地 ahead 提交。
- 修改:更新 `agent-change-log`,新增 `Required Git Check`,要求写日志前执行 `git fetch --all --prune``git status -sb`、上游分支识别和 `git log HEAD..@{u}`
- 修改:更新 Skill 展示提示,把 upstream commit check 也列入触发场景。
- 修改:更新 `AGENTS.md`,明确每次日志更新前先做 Git 拉取检查;工作区干净且只落后上游时可 `git pull --ff-only`,脏工作区或可能冲突时只 fetch 并记录风险。
- 操作:没有在当前脏工作区执行自动 merge/rebase避免把其他大量未提交改动和远端提交混在一起。
- 验证:`rg` 已命中 `Required Git Check``git fetch --all --prune``HEAD..@{u}``git pull --ff-only``git diff --check` 无输出。
- 影响:后续日志会同时记录“我改了什么”和“其他智能体已经提交了什么”,不会只盯着本地 diff。
- 10:02我把本地 ahead 提交也纳入日志 Skill因为当前仓库已经出现 `origin/main..HEAD` 的本地提交。
- Git 提交检查:`origin/main..HEAD` 发现 `9321260 chore(skills): add git checkpoint commit loop`,提交时间为 2026-06-24 09:47:05内容包括新增 `git-checkpoint-commit` Skill、展示元数据、`checkpoint_commit.py` 脚本,并调整 `.gitignore`
- 修改:更新 `agent-change-log`,要求同时检查 `HEAD..@{u}``@{u}..HEAD`,分别识别 upstream 新提交和本地 ahead 新提交。
- 修改:更新 `AGENTS.md`,把本地 ahead 提交也纳入“其他智能体提交摘要”的记录范围。
- 操作:用 `git show --stat``git show --name-only` 查看 `9321260` 的文件清单与提交信息,没有改写该提交。
- 验证:`rg` 已命中 `@{u}..HEAD``本地 ahead``9321260``git diff --check` 无输出。
- 影响:后续即使其他智能体只做了本地 commit、还没 push也会被日志 Skill 捕捉并分析。
- 10:21自动记录 `9321260` 提交后的工作日志。auto-log:9321260
- Git 提交检查fetch 失败error: cannot open '.git/FETCH_HEAD': Operation not permittedupstream `origin/main`upstream 新提交:未发现;本地 ahead 提交9321260 (HEAD -> main) chore(skills): add git checkpoint commit loop。
- 修改:最近提交为 `9321260 chore(skills): add git checkpoint commit loop`
- 操作manual 验证自动日志脚本 触发 `tools/agent-change-log/update_change_log.py`,自动读取 Git 状态并写入当天日志。
- 验证:自动脚本只记录提交和 Git 状态,不替代业务测试;业务验证仍以本次任务实际运行结果为准。
- 影响:提交后即使执行代理忘记手动写日志,也会留下最低限度的时间、提交和分支状态记录。
- 10:21我排查了“修改 bug 或新增功能后不会自动写日志”的根因,并补上可执行自动化入口。
- Git 提交检查:当前权限下 `git fetch --all --prune` 会失败,错误是 `.git/FETCH_HEAD` 不允许写入;`origin/main..HEAD` 仍显示本地 ahead 提交 `9321260`
- 修改:新增 `tools/agent-change-log/update_change_log.py`,用于自动生成并追加当天工作日志,包含 Git 双向提交检查、最近提交摘要、重复提交去重和 fetch 失败遗留问题记录。
- 修改:新增 `.githooks/post-commit`,提交后调用日志脚本,作为真正的自动触发点。
- 修改:新增 `tools/agent-change-log/install_post_commit_hook.sh``README.md`,说明如何把版本化 hook 安装到本地 `.git/hooks/post-commit`
- 修改:更新 `AGENTS.md`,明确 Skill/AGENTS 只是规则,自动触发必须安装 post-commit hook如果当前环境无法写 `.git`,必须如实记录限制。
- 操作:尝试执行 `tools/agent-change-log/install_post_commit_hook.sh`,当前沙箱返回 `mkdir: .git/hooks: Operation not permitted`,所以本轮不能实际安装本地 hook。
- 验证:`update_change_log.py --dry-run --no-fetch` 能输出日志条目;默认 dry-run 能捕获 fetch 权限错误并输出遗留问题;真实运行脚本已成功追加 10:21 的 `auto-log:9321260` 记录。
- 影响:现在仓库里已经有可执行自动日志链路;等环境允许写 `.git` 后,安装 hook 即可实现“提交后自动写日志”。未提交的普通文件变更仍无法由 Git 自动判断,需要代理在任务结束前主动调用日志 Skill。
- 10:24我修正了 PDF 票据 OCR 的转图链路,避免“预览图丢中文但仍继续 OCR”的假成功。
- Git 提交检查:`git fetch --all --prune` 失败,错误是 `.git/FETCH_HEAD: Operation not permitted``git status -sb` 显示当前 `main...origin/main [ahead 1]``HEAD..@{u}` 未输出 upstream 新提交;`@{u}..HEAD` 显示本地 ahead 提交 `9321260 chore(skills): add git checkpoint commit loop`
- 修改:`document_preview.py` 把 PDF 渲染器标识升级为 `pdf-raster-cjk-safe-v3`,新增 `render_pdf_pages()`,按 `pdftoppm -> mutool -> gs -> pdftocairo` 尝试生成 PNG遇到 `Missing language pack``Unknown font tag``No font in show` 这类会丢中文的输出时,不再接受该 PNG。
- 修改:`ocr.py` 删除“PDF 文本层可用就直接构建识别结果”的分支PDF 现在必须先转图片,再进入 Paddle OCR worker文本层仅作为后续纠错/兜底材料,不再绕过图片 OCR。
- 修改:`docker-compose.yml``docker-compose.full.yml``bootstrap_paddleocr_mobile.sh``bootstrap_paddleocr_gpu.sh` 增加 `mupdf-tools`,让容器里有 `mutool` 作为中文安全转图 fallback。
- 修改:`test_ocr_service.py` 增加回归测试,覆盖运行时依赖包含 `mupdf-tools`、Poppler 字体映射失败时尝试下一个渲染器、坏转图不再调用 OCR worker、以及 PDF 文本层可用时仍必须调用 OCR worker。
- 操作:执行 `python3 -m py_compile``git diff --check``rg` 残留检查;尝试按项目规范执行容器内定向 pytest。
- 验证:`py_compile``git diff --check`、残留搜索均通过;容器 pytest 未能执行,原因是当前 Codex 进程访问 Docker socket 被拒绝:`permission denied while trying to connect to the docker API at unix:///Users/caoxiaozhu/.docker/run/docker.sock`
- 影响:后续 PDF 票据如果第一轮转图丢中文,会自动切换渲染器;如果所有渲染器都不可用,会明确失败并提示转图问题,而不是把缺中文的坏图送进 OCR。
- 10:28我确认 10:06 上传的票据夹记录当前仍是 PDF 预览,并修正 OCR 文本层纠错会清空 PNG 预览的问题。
- Git 提交检查:`git fetch --all --prune` 失败,错误是 `.git/FETCH_HEAD: Operation not permitted`;当前 `main...origin/main [ahead 1]``HEAD..@{u}` 未输出 upstream 新提交;`@{u}..HEAD` 仍显示本地 ahead 提交 `9321260 chore(skills): add git checkpoint commit loop`
- 发现:`server/storage/receipt_folder/caoxiaozhu_xf.com/f0dd9b4a-f7b7-4784-ade7-4e31b71f0b12/meta.json``preview_kind=pdf``preview_media_type=application/pdf`,前端 `ReceiptFolderView.vue` 因此走 `<iframe>` 而不是 `<img>`
- 根因:`OcrService._finalize_document()` 里旧逻辑在 PDF 文本层参与纠错且 OCR 文本占位符比例较高时,会主动把 `preview_kind``preview_data_url` 清空;票据夹保存时拿不到 `data:image/png`,只能回退到原 PDF 预览。
- 修改:删除这段清空 PNG 预览的旧保护逻辑;转图阶段已经负责识别坏 PNG文本层纠错不再影响预览资产类型。
- 操作:执行 `python3 -m py_compile server/src/app/services/ocr.py server/tests/test_ocr_service.py``git diff --check``rg` 残留检查。
- 验证:语法检查、空白检查和残留搜索均通过;容器 pytest 仍因 Docker socket 权限无法执行。
- 影响:后续 PDF OCR 结果即使使用文本层修正字段,也会保留 OCR 转图得到的 PNG 预览,票据夹应写入 `preview_kind=image``preview_media_type=image/png`
- 10:32我继续排查“重新上传仍是 PDF 识别”的原因,确认最新 10:29 上传命中了旧 OCR 结果,并修正 OCR 内存缓存失效条件。
- Git 提交检查:`git fetch --all --prune` 失败,错误是 `.git/FETCH_HEAD: Operation not permitted`;当前 `main...origin/main [ahead 1]``HEAD..@{u}` 未输出 upstream 新提交;`@{u}..HEAD` 仍显示本地 ahead 提交 `9321260 chore(skills): add git checkpoint commit loop`
- 发现:最新两条票据夹记录 `e9e3c0be-...``9ecee0d8-...` 上传时间为 10:29但仍是 `preview_kind=pdf``ocr_line_count=0``ocr_text` 仍是只有英文和数字的旧坏结果。
- 根因:`OcrService._build_cache_key()` 只使用 OCR 语言、设备、模型和文件 digest没有包含 PDF 转图/识别管线版本;同一 PDF 重新上传时可能复用旧的内存缓存,导致新转图逻辑没有机会执行。
- 修改:新增 `OCR_RESULT_CACHE_PIPELINE_VERSION`,把当前 PDF 渲染器标识 `DocumentPreviewAssets.PDF_RENDERER_ID``no-pdf-direct` 策略写入 OCR cache key。
- 修改:`test_ocr_service.py` 增加 `test_ocr_cache_key_includes_pdf_render_pipeline_version`,防止以后改转图管线但继续吃旧缓存。
- 操作:执行 `python3 -m py_compile server/src/app/services/ocr.py server/tests/test_ocr_service.py``git diff --check``rg` 检查;尝试执行容器内定向 pytest。
- 验证:本地语法检查、空白检查和残留搜索通过;容器 pytest 仍因 Docker socket 权限被拒绝,未能执行。
- 影响:后端加载新代码后,同一 PDF 会因为 cache key 变化重新进入 PDF 转 PNG + Paddle OCR 流程,而不是复用旧的 PDF fallback/坏识别结果。
- 10:40我修正了 AI 工作台附件预览弹窗在左侧侧边栏存在时视觉不居中的问题。
- Git 提交检查:`git fetch --all --prune` 失败,错误是 `.git/FETCH_HEAD: Operation not permitted`;当前 `main...origin/main [ahead 1]`;基于本地 ref`HEAD..@{u}` 未输出 upstream 新提交;`@{u}..HEAD` 仍显示本地 ahead 提交 `9321260 chore(skills): add git checkpoint commit loop`
- 修改:`workbench-ai-file-preview-dialog.css` 将预览遮罩改成“侧边栏占位列 + 主内容列”的网格布局,弹窗固定落在第二列并居中;同时保留折叠侧栏变量和 900px 以下移动端单列布局。
- 修改:`workbench-ai-composer-components.test.mjs` 增加回归断言,锁定附件预览必须按主内容区域居中,而不是按整个 viewport 居中。
- 操作:检查本地 `5173` 可达,返回 `HTTP/1.1 200 OK`;检查项目没有现成 Playwright/Puppeteer 依赖,未新增依赖。
- 验证:`node --test web/tests/workbench-ai-composer-components.test.mjs` 通过 8/8`npm --prefix web run build` 构建通过;`git diff --check` 无输出。
- 影响:用户点击附件打开预览时,弹窗会避开左侧 AI 工作台侧边栏,在右侧主工作区内居中展示,截图里的“偏左/不居中”观感会收敛。
- 11:06我重设计了系统设置里的缓存管理页面让它从“单个按钮 + 原始错误块”变成可读的维护工具页。
- Git 提交检查:`git fetch --all --prune` 失败,错误是 `.git/FETCH_HEAD: Operation not permitted`;当前 `main...origin/main`,基于本地 ref`HEAD..@{u}``@{u}..HEAD` 均未输出新提交。
- 修改:`SettingsView.vue` 将缓存管理区改为维护页式结构包含顶部说明、清理范围概览、安全说明、4 类缓存范围清单、维护操作条和结果反馈区。
- 修改:`settings-view.css` 新增缓存管理布局样式,桌面 4 列、平板 2 列、手机 1 列;整体收敛为企业后台风格,减少截图里的空泛卡片感。
- 修改:`useSettings.js` 增加 `cacheClearFailed``normalizeCacheClearErrorMessage()`,把后端原始 `Not Found` 映射为“缓存清理接口暂不可用,请确认后端服务已加载最新路由后重试。”。
- 修改:`settings-cache-management-section.test.mjs` 增加回归断言,锁定范围清单、保护说明、失败态和友好错误文案。
- 操作:检查本地 `http://127.0.0.1:5173/app/settings?section=cacheManagement` 可达,返回 `HTTP/1.1 200 OK`;确认项目没有现成 Playwright/Puppeteer未新增浏览器依赖。
- 验证:`node --test web/tests/settings-cache-management-section.test.mjs` 通过 3/3`npm --prefix web run build` 构建通过;`git diff --check` 无输出。
- 影响:缓存管理页现在能清楚说明“清什么、不清什么、执行后结果如何”,异常时不再直接显示生硬的 `Not Found`
- 10:41我补修了票据夹 PDF 保存阶段的预览持久化,避免 OCR 后仍把源 PDF 当成附件预览展示。
- Git 提交检查:`git fetch --all --prune` 失败,错误是 `error: cannot open '.git/FETCH_HEAD': Operation not permitted`;当前 `main...origin/main [ahead 1]`;基于本地 ref`HEAD..@{u}` 未输出 upstream 新提交;`@{u}..HEAD` 显示本地 ahead 提交 `9321260 chore(skills): add git checkpoint commit loop`
- 修改:`receipt_folder.py``document.preview_data_url` 缺失且源文件是 `application/pdf` 时,保存阶段立即调用 `DocumentPreviewAssets.render_pdf_first_page()` 生成 `preview.png`,并把 `preview_kind``preview_media_type``preview_rendered_with` 写成图片预览元数据;只有渲染异常时才回退到源 PDF 预览。
- 修改:`test_receipt_folder_service.py` 新增 `test_receipt_folder_pdf_save_eagerly_renders_image_preview`,覆盖 PDF OCR 结果没有内嵌预览图时,票据夹仍应主动生成图片预览的场景。
- 操作:读取当前票据夹存储里的两条 PDF meta确认旧记录存在 `preview_kind=pdf``preview_media_type=application/pdf` 且没有 `preview.png`,这与前端显示 PDF 预览的问题一致。
- 验证:`python3 -m py_compile server/src/app/services/receipt_folder.py server/tests/test_receipt_folder_service.py` 通过;宿主机缺少 pytest 和后端依赖,容器 pytest 又因 Docker socket 权限被拒绝,暂未完成项目要求的容器定向测试。
- 影响:后续新上传或重新 OCR 保存的 PDF 票据会优先拥有 PNG 图片预览,前端票据夹预览应走 `<img>` 体验;既有已经写成 PDF fallback 的旧 meta 还需要单独刷新。
- 10:56我把系统设置里的 `Agent Trace` 分区替换为“缓存管理”,补上管理员一键清理进程内缓存的前后端链路。
- Git 提交检查:`git fetch --all --prune` 失败,错误是 `error: cannot open '.git/FETCH_HEAD': Operation not permitted`;当前 upstream 为 `origin/main`;基于本地 ref`HEAD..@{u}` 未输出 upstream 新提交,`@{u}..HEAD` 未输出本地 ahead 提交。
- 修改:后端新增 `SystemCacheService``/api/v1/settings/cache/clear`,清理 OCR 识别结果缓存、运行时配置缓存、模型失败冷却缓存、知识库本地索引缓存和地点语义分析缓存,并返回每项清理数量;接口使用管理员依赖保护,不删除票据源文件、业务单据或数据库记录。
- 修改:`OcrService``runtime_chat.py``knowledge_rag_local.py``application_location_semantics.py``config.py` 增加可计数的缓存清理入口,避免继续靠重启服务才能摆脱旧 OCR 结果。
- 修改:前端 `settingsModelHelper.js``agentTraces` 分区替换为 `cacheManagement``SettingsView.vue` 增加“应用缓存”面板和“一键清理缓存”按钮;`useSettings.js` 接入 `clearSystemCaches()`,展示清理中、成功/失败和各缓存项数量;同时移除系统设置里对 `AgentTraceCenterView` 的加载。
- 修改:移除 `LogDetailView.vue``DigitalEmployeeWorkRecords.vue` 中跳转到已删除 `agentTraces` 设置分区的“查看 Trace”按钮避免用户点到死链。
- 修改:新增 `test_system_cache_endpoints.py``settings-cache-management-section.test.mjs`;顺手把设置页既有渲染/LLM 测试的断言目标从外层组件对齐到当前真实模型/子组件,并把渲染设置卡片间距恢复为测试要求的 24px。
- 操作:执行前端设置相关测试、`npm --prefix web run build`、Python `py_compile``git diff --check` 和旧 Agent Trace 设置入口残留搜索。
- 验证:前端设置测试全部通过,`npm --prefix web run build` 通过Python 编译通过,`git diff --check` 无输出;容器 pytest 仍因 Docker socket 权限被拒绝,未能执行 `server/tests/test_system_cache_endpoints.py`
- 影响:管理员可以在系统设置里手动清掉 OCR 等进程内缓存;生产上如果某次 OCR 结果错误,不必依赖重启服务才能让同一附件重新走识别链路。
- 10:57我复查了“重新上传后仍是 PDF 预览”的运行时证据,并补了前端按实际预览 blob 类型纠正展示方式的回归修复。
- Git 提交检查:`git fetch --all --prune` 失败,错误是 `error: cannot open '.git/FETCH_HEAD': Operation not permitted`;当前 `main...origin/main`;基于本地 ref`HEAD..@{u}``@{u}..HEAD` 均未输出提交。
- 发现:最新票据 `25be8906-d3c8-4236-934d-e769ee19d3a7``meta.json` 仍是 `preview_kind=pdf``preview_media_type=application/pdf`,目录里没有 `preview.png`;同时 10:43 的 `POST /api/v1/ocr/recognize` 只耗时 9ms说明运行中后端仍在吃旧 OCR 缓存,或尚未重启加载新代码。
- 发现10:41 之后 `server/logs/app.log` 没有新的 `Starting X-Financial`,所以用户随后测试时后端没有加载刚改的保存阶段预览逻辑;当前 Codex 沙箱执行 `docker ps` / `docker exec` 仍被 Docker socket 权限拒绝,无法替用户重启或检查容器内 `mutool`
- 修改:`ReceiptFolderView.vue``loadPreview()` 取回 blob 后,根据 `blob.type` 推断实际预览类型;如果后端懒刷新后返回 `image/png`,即使详情 JSON 里还是旧的 `preview_kind=pdf`,页面也会把 `selectedReceipt.preview_kind` 修正为 `image` 并走 `<img>`
- 修改:`receipt-folder-view.test.mjs` 新增静态回归断言,锁定 `inferPreviewKindFromBlob()``image/*``application/pdf``loadPreview()` 更新 `preview_kind` 的行为。
- 验证:先运行 `node --test web/tests/receipt-folder-view.test.mjs` 看到新增断言红灯;实现后同一测试通过;`npm --prefix web run build` 通过;`git diff --check` 无输出。容器内后端 pytest/运行时工具检查仍因 Docker socket 权限无法执行。
- 影响:根因层面仍需要重启/重建容器后端加载新代码和 PDF 渲染工具;前端层面已经避免“预览接口返回图片,但详情旧 kind 仍让 UI 当 PDF 展示”的二次误判。
- 11:09我排查了“一键清理缓存”点击后返回 `Not Found` 的原因,并给后端路由表补了 OpenAPI 回归断言。
- Git 提交检查:`git fetch --all --prune` 失败,错误是 `error: cannot open '.git/FETCH_HEAD': Operation not permitted`;当前 upstream 为 `origin/main`;基于本地 ref`HEAD..@{u}``@{u}..HEAD` 均未输出新提交。
- 发现:`server/logs/app.log` 已记录 `POST /api/v1/settings/cache/clear 404`,说明浏览器请求路径没有走偏,确实打进了当前 FastAPI 后端,但运行中的路由表还没包含新接口。
- 发现:后端最后一次启动日志是 10:01 左右,而 `settings.py` 的缓存清理路由是 10:49 后写入;后续没有新的 `Starting X-Financial``touch settings.py` 也没有触发 reload 日志,判断当前容器后端未热重载新代码。
- 修改:`test_openapi_schema.py` 增加 `/api/v1/settings/cache/clear` 的 OpenAPI 断言,要求运行时路由表必须暴露“清理系统缓存”接口,防止后续代码改了但 router 没挂上仍然漏测。
- 操作:尝试用 `docker ps``docker exec` 进入 `local-x-financial-linux`,以及通过 `2223` SSH 进入容器;当前 Codex 沙箱分别被 Docker socket 权限和本机网络策略拒绝,不能直接替用户重启容器或在容器内执行 pytest。
- 验证:`python3 -m py_compile server/tests/test_openapi_schema.py server/src/app/api/v1/endpoints/settings.py server/src/app/schemas/settings.py server/src/app/services/system_cache.py` 通过;`node web/tests/settings-cache-management-section.test.mjs` 通过 3/3容器内 `pytest server/tests/test_system_cache_endpoints.py server/tests/test_openapi_schema.py` 未能执行,原因仍是 Docker socket 权限拒绝。
- 影响:代码层面的接口和回归测试已经补齐;当前页面的 `Not Found` 需要运行中的后端容器重启/热重载后才会消失。
- 11:15我根据你的要求对系统设置里的缓存管理页面进行了全面的视觉和体验重设计升级为具备高级质感和微交互的控制台。
- Git 提交检查:`git fetch --all --prune` 成功执行;当前 `main...origin/main``HEAD..@{u}` 未输出 upstream 新提交;`@{u}..HEAD` 输出部分本地未 push 提交(如上文所述)。
- 修改:`SettingsView.vue` 调整了缓存管理的 DOM 结构,引入了如 `item-ocr`, `item-model` 等特定类名用于精细化配置图标颜色,将四类清理范围展示为动态可交互的卡片。
- 修改:`settings-view.css` 增加了微动效(卡片悬停浮起、图标放大效果)、阴影层级、渐变背景以及平滑过渡,同时升级了“一键清理缓存”的操作按钮质感和渐变绿色安全提示带。
- 验证:本地确认代码结构变更正确无冲突。由于当前沙箱没有可用浏览器自动化插件,未生成真实页面截图验证。
- 影响:缓存管理页面的视觉提升到了更专业现代的企业控制台风格。
- 11:19根据你提供的 ONLYOFFICE 页面截图,我发现你更偏好简洁平滑的系统原生表单设计,而不是之前那些丰富渐变的元素。
- 修改:撤销了刚刚给 `SettingsView.vue``settings-view.css` 中添加的高级渲染效果(去除了所有悬浮渐变、卡片光晕和彩色图标),并恢复为了扁平简约的标准卡片列表样式。
- 影响:现在“应用缓存清理”的样式已经和系统其他选项卡保持了更好的统一。
- 11:23针对“视觉上依然颜色居多”的反馈我进一步清除了缓存管理面板遗留的底层硬编码色彩。
- 发现:在我最初接手前,缓存管理的“清理面板”带有橙色背景(`#fffaf5`)和橙色边框(`#fed7aa`),“安全提示条”带有绿色背景(`#f0fdf4`)和绿色边框(`#bbf7d0`)。
- 修改:在 `settings-view.css` 中,将 `.cache-management-panel``.cache-safety-strip``.cache-clear-button` 的这些定制颜色全部移除,统一使用与系统基调一致的 `#e2e8f0` 边框、`#f8fafc` 浅灰背景和 `#334155` 标准文本色。
- 影响现在整个“缓存管理”页面真正做到了去色彩化和截图里“ONLYOFFICE”配置页一样纯粹克制。
- 11:29我修复了安全提示条和操作面板“顶头”撑满整个卡片边缘的问题。
- 发现:之前这两个模块作为 `.settings-card` 的直接子元素,导致父级的 `padding-left: 24px; padding-right: 24px;` 直接作用在它们身上,而它们自己又带有边框和背景,结果就是在视觉上“边框贴着父容器的边缘”。
- 修改:在 `SettingsView.vue` 中,给这两个模块外面包裹了一层标准的 `<div>` 容器。
- 影响:现在父级的 padding 被外层 `div` 吸收,安全提示条和操作面板恢复了正常的内缩边距,完美对齐了中间的 4 个范围网格,不再有“顶破边界”的突兀感。
- 11:20我继续排查“一键清理缓存”仍不可用并修正容器名与容器 reload 启动配置。
- Git 提交检查:`git fetch --all --prune` 失败,错误是 `error: cannot open '.git/FETCH_HEAD': Operation not permitted`;当前 upstream 为 `origin/main`;基于本地 ref`HEAD..@{u}``@{u}..HEAD` 均未输出新提交。
- 发现:`server/logs/app.log` 继续记录 03:07、03:11、03:14 的 `POST /api/v1/settings/cache/clear 404`;请求仍然打进 FastAPI但运行路由表仍是旧进程。
- 发现:`server/server_start.sh` 在容器内默认关闭 `SERVER_RELOAD`,而外层 `start.sh` 之前没有容器判断,会误以为 `APP_DEBUG=true` 就是 reload 模式,导致复用旧后端时没有提示。
- 修改:`AGENTS.md``docker-compose.yml``docker-compose.full.yml` 将主容器名统一为 `local-x-financial-linux`compose 同时显式注入 `SERVER_RELOAD=${SERVER_RELOAD:-true}`,让本地开发容器后端默认开启 uvicorn reload。
- 修改:`start.sh` 增加 `is_container()` 判断,容器内默认 reload 状态与 `server/server_start.sh` 保持一致;如果没有显式开启 reload复用既有 FastAPI 时会准确提示可能是旧后端。
- 修改:`settings-cache-management-section.test.mjs` 对齐当前缓存管理页 DOM 结构,避免继续断言已经不存在的旧 `cache-management-hero` 容器。
- 验证:`sh -n start.sh && sh -n server/server_start.sh` 通过;`python3 -m py_compile ...` 通过;`node web/tests/settings-cache-management-section.test.mjs` 通过 3/3`git diff --check` 无输出;容器内 pytest 仍因 Docker socket 权限被拒绝。
- 影响:重启/重建 `local-x-financial-linux` 后,缓存清理接口应加载进运行路由表;后续本地开发容器里的后端改动也不会再静默停留在旧进程。
- 11:39我修复了“清缓存后对话归集附件详情页变成 PDF 预览、识别信息退化为其他单据”的问题。
- Git 提交检查:`git fetch --all --prune` 失败,错误是 `error: cannot open '.git/FETCH_HEAD': Operation not permitted`;当前 upstream 为 `origin/main`;基于本地 ref`HEAD..@{u}``@{u}..HEAD` 均未输出新提交;工作区仍有多项既有未提交改动。
- 根因:对话上传附件先写入票据夹,再归集到报销单附件;归集时会重新跑一次附件 OCR如果这次结果没有 `preview_data_url`,即使票据夹里已有 PNG 预览,报销单附件 meta 仍会回退到源 PDF。清 OCR 缓存后,详情页不再有会话临时结果兜底,就暴露为 PDF 预览和“其他单据”。
- 修改:`expense_claim_attachment_operations.py` 读取 `source_receipt_id` 对应的票据夹预览资源,并在 OCR 来源选择时保留不弱于新 OCR 且带图片预览的票据夹结果,避免被一次同等质量但无预览图的新 OCR 覆盖。
- 修改:`expense_claim_attachment_presentation.py` 支持把票据夹已有的 PNG 预览复制到报销单附件目录,写入 `preview_kind=image``preview_media_type=image/png` 和新的 `preview_storage_key`
- 修改:`expense_claim_attachment_document.py` 增加历史坏 meta 自动修复:当附件 meta 仍有 `source_receipt_id`,且当前是 PDF 预览、`other` 类型或无字段时,详情页读取 meta/预览会从票据夹重新补回 OCR 字段和 PNG 预览。
- 修改:`test_attachment_association_jobs.py` 增加两条回归测试,分别覆盖清 OCR 缓存后归集仍保留票据夹 PNG/字段,以及已经退化成 PDF/其他单据的历史附件能通过 `source_receipt_id` 自动修复。
- 验证:`python3 -m py_compile server/src/app/services/expense_claim_attachment_presentation.py server/src/app/services/expense_claim_attachment_operations.py server/src/app/services/expense_claim_attachment_document.py server/tests/test_attachment_association_jobs.py` 通过;`git diff --check` 通过;容器内定向 pytest 仍因 Docker socket 权限被拒绝,命令未能进入 `local-x-financial-linux`
- 影响:后续从 AI 对话上传并自动归集的 PDF 票据,应在单据详情页展示报销单附件目录里的 PNG 预览,并稳定保留火车票字段;已经坏写入的附件只要 meta 里还保留 `source_receipt_id`,打开详情页时会尝试自动修复。
- 11:55我修复了 AI 工作台上传 PDF 后 OCR 退化成“其他单据/空字段”的问题,并修复了同文件重复上传继续复用旧坏 meta 的链路。
- Git 提交检查:`git fetch --all --prune` 成功;当前 `main...origin/main``HEAD..@{u}``@{u}..HEAD` 均未输出新提交;工作区仍有多项既有未提交改动,本轮只新增 OCR/票据夹相关修改。
- 根因:运行容器缺 `poppler-data` / `mutool`,中文 PDF 转图失败;`OcrService` 虽然先提取了 `pdftotext` 文本层,但 `_prepare_pdf_inputs()` 转图失败后直接返回失败结果,文本层没有机会进入分类和字段抽取。
- 根因:同一 PDF 之前已经以 `other/空字段` 写入票据夹时,`persist_ocr_batch()` 的重复文件路径会读取旧 meta 并覆盖新 OCR 结果,导致用户重新上传同一文件仍看到“当前会话已识别 · 其他单据”。
- 修改:`ocr.py` 增加 PDF 文本层兜底结果构建;当转图失败但文本层有有效字符时,继续生成结构化识别结果,同时保留转图失败 warning不把坏 PNG 当成预览。
- 修改:`receipt_folder.py` 在重复文件命中旧票据时,如果新 OCR 结果明显更强(非 `other`、有字段、有文本),会刷新旧票据的 OCR 派生 meta再返回带重复上传 warning 的新结果;同时把“身份证号”这类标签排除出乘车人候选,避免字段误填。
- 修改:`test_ocr_service.py``test_receipt_folder_service.py` 增加红绿回归,覆盖 PDF 转图失败但文本层可用、旧坏 meta 被新 OCR 修复,以及测试间 OCR cache 隔离。
- 操作:重启当前实际运行容器 `x-financial-local-linux`,等待 `http://127.0.0.1:5173/api/v1/health` 恢复;随后用 `caoxiaozhu@xf.com` 用户头对 `2月20_武汉-上海.pdf``2月23_上海-武汉.pdf` 重新调用 `/api/v1/ocr/recognize`,修复同批两条旧坏票据 meta。
- 验证:容器内 `pytest -q server/tests/test_ocr_service.py server/tests/test_receipt_folder_service.py server/tests/test_ocr_endpoints.py server/tests/test_attachment_association_jobs.py` 通过 28/28`python -m py_compile` 通过;`git diff --check` 无输出;真实 5173 OCR 接口返回两张 PDF 均为 `火车/高铁票`,字段包含时间、车次、行程、金额、身份证号、车厢、座位号和商户。
- 影响:后续即使中文 PDF 转图依赖暂时缺失AI 工作台也不再把可读文本层的火车票退化成“其他单据”;已坏写入的同文件重复上传会被新 OCR 结果修复,而不是继续复用旧空字段。
- 12:04我补齐当前实际运行容器的 PDF 渲染依赖,并把同批两张火车票 PDF 的预览刷新成 PNG 图片。
- Git 提交检查:`git fetch --all --prune` 成功;当前 `main...origin/main``HEAD..@{u}``@{u}..HEAD` 均未输出新提交;工作区仍有多项既有未提交改动。
- 根因:仓库 `docker-compose.yml` 已写入 `poppler-data mupdf-tools`,但当前占用 5173 的仍是旧容器 `x-financial-local-linux`,它没有按最新 compose 重建,容器内只有 `pdftoppm/pdftocairo/pdftotext`,缺 `poppler-data``mutool`,所以后端只能回退到源 PDF 预览。
- 操作:在当前开发容器内执行 `apt-get update && apt-get install -y --no-install-recommends poppler-data mupdf-tools`;随后访问两条 receipt 的 `/preview` 接口触发 `_refresh_pdf_preview_asset_if_needed()` 生成 `preview.png`
- 验证:两个 `/api/v1/receipt-folder/{id}/preview` 响应都已从 `application/pdf` 变为 `image/png``file` 确认为 `PNG image data, 1323 x 882`;两条 `meta.json` 均写入 `preview_kind=image``preview_media_type=image/png``preview_file_name=preview.png``preview_rendered_with=pdf-raster-cjk-safe-v3`;人工查看 `preview.png` 确认中文票面正常渲染。
- 影响:当前 5173 页面重新打开这两条票据预览时应直接走图片预览;后续当前容器内新上传同类中文 PDF 也会优先生成 PNG 预览。
- 12:23我把会话上传附件、票据夹和报销附件相关的预览判定收敛到统一的前端预览资产模型并让 OCR 返回带回票据夹图片预览类型。
- Git 提交检查:`git fetch --all --prune` 成功;当前 upstream 为 `origin/main``HEAD..@{u}``@{u}..HEAD` 均未输出新提交;工作区仍有多项既有未提交改动。
- 根因AI 会话上传附件条之前只按原始 `File.type/name` 判断卡片类型,所以 PDF 票据即使 OCR 已识别为火车票、票据夹预览接口已返回 PNG卡片仍显示 PDF弹窗也只优先吃 `preview_data_url`,没有统一处理 `receipt_preview_url`
- 修改:新增 `documentPreviewAssets.js`统一提供文件、URL、blob、OCR document 的预览类型/资产解析;`travelReimbursementAttachmentModel.js``ReceiptFolderView.vue``workbenchAiComposerModel.js``useWorkbenchAiFilePreview.js` 改为复用同一套解析逻辑。
- 修改:会话附件卡片在 OCR document 带 `preview_kind=image` + `receipt_preview_url` 时优先显示图片类图标,不再继续按原始 PDF 类型展示;附件预览弹窗遇到票据夹预览接口时通过 `fetchReceiptFolderAsset()` 带鉴权拉取 blob再按真实 `Content-Type` 选择图片/PDF 展示。
- 修改:`receipt_folder.py` 在 OCR 持久化后把票据夹 meta 里的 `preview_kind` 回填到返回给会话层的 OCR document新增后端回归测试覆盖 PDF OCR document 持久化后返回 `preview_kind=image`
- 验证:前端 `node --test web/tests/attachment-association-confirmation.test.mjs web/tests/workbench-ai-composer-components.test.mjs` 通过 24/24`node --test web/tests/receipt-folder-view.test.mjs` 通过;容器内 `pytest -q server/tests/test_ocr_endpoints.py server/tests/test_ocr_service.py server/tests/test_receipt_folder_service.py` 通过 25/25`npm --prefix web run build` 通过;`python -m py_compile` 通过;`git diff --check` 无输出。
- 操作:重启当前实际运行容器 `x-financial-local-linux` 并确认 `/api/v1/health` 正常;真实 5173 `/api/v1/ocr/recognize` 重新上传 `2月23_上海-武汉.pdf` 后返回 `document_type=train_ticket``preview_kind=image``receipt_preview_url=/receipt-folder/.../preview`,对应 `/preview` 响应 `content-type: image/png`
- 影响AI 会话上传 PDF 火车票后,附件条和预览弹窗都会走统一预览资产判定;后续其它入口只要使用 `documentPreviewAssets.js`,就不会再各自维护一套 PDF/图片判断。
- 12:52我重新修改了一键关联票据对话框的 UI 样式,并根据“卡片过大、不够精致”的反馈进行了尺寸收敛与精细化微调。
- Git 提交检查:`git fetch --all --prune` 成功执行;`HEAD..origin/main``origin/main..HEAD` 均无提交。
- 修改:在 `receipt-folder-view.css` 中,为弹窗外框加上轻量圆角 (6px) 与精致投影;将弹窗 header padding 收至 `20px 24px 12px 24px`body padding 收至 `0 24px 20px 24px`,确保紧凑挺拔。
- 修改:将列表行间距设为 `8px`,每个条目高度收敛至 `52px`,内边距调整为 `10px 14px`,文件名与说明文本大小降为 `13px``11.5px`,以求更加精致细腻。
- 修改:将底部操作区的按钮高度设为标准 `36px`,水平内边距设为 `16px`,字重保持专业的 `500`,使比例更协调,消除挤压感的同时保留小巧玲珑感。
- 操作:执行前端生产编译构建 `npm run build` 并运行 `node --test web/tests/receipt-folder-view.test.mjs`
- 验证:`npm run build` 构建成功,单元测试通过,`git diff --check` 无输出。
- 影响:弹窗彻底摆脱了原本的拥挤挤压感,同时避免了尺度过大、粗糙的问题,整体显得小巧精致、清爽洗练。
- 13:03我把 AI 工作台里的“去上海出差,辅助国网仿生产服务器部署,交通火车,直接提交”接成首轮完整申请链路,不再落回一步步追问。
- Git 提交检查:`git fetch --all --prune` 已在 Git 写权限升级后成功执行;当前 upstream 为 `origin/main``HEAD..@{u}``@{u}..HEAD` 均未输出新提交;工作区仍有多项既有未提交改动,本轮只触碰 AI 工作台申请意图与测试文件。
- 修改:`workbenchAiApplicationGateModel.js` 新增 `resolveInlineTravelApplicationRequest()`,识别“出差/差旅/交通 + 具体地点或交通方式”的申请意图,并排除查标准、问制度、查进度等咨询类请求。
- 修改:`usePersonalWorkbenchAiMode.js` 在已有申请表动作之后、报销创建入口之前接入差旅申请识别;命中后直接调用 `startAiApplicationPreview()`,并把同句里的“直接提交”转成 `autoSubmit`
- 修改:`useWorkbenchAiApplicationPreviewFlow.js` 支持申请预览生成后自动提交;只有 `normalizeApplicationPreview(preview).readyToSubmit` 为真时才带 `confirmed: true` 复用现有提交前核查和提交动作,缺日期等必填项时仍停在核对表。
- 修改:`workbench-ai-application-gate-model.test.mjs``expense-application-fast-preview.test.mjs` 增加回归测试,锁定紧凑差旅直提句子的识别、分流和自动提交接线。
- 验证:`node --test web/tests/workbench-ai-application-gate-model.test.mjs` 通过 4/4`node --test --test-name-pattern "direct-submit" web/tests/expense-application-fast-preview.test.mjs` 通过 2/2`git diff --check -- ...` 无输出;`npm --prefix web run build` 构建通过。
- 验证补充:整跑 `node --test web/tests/expense-application-fast-preview.test.mjs` 时,本次新增用例通过,但该文件仍有 12 个既有失败,集中在其它文案/静态结构断言,已单独记录为遗留问题。
- 影响用户把差旅目的地、事由、交通方式和“直接提交”放在一句话里时AI 工作台会直接生成申请核对表;字段齐全时自动进入提交前核查和提交,不再用多轮问答确认同一件事。
- 13:20我先落了 AI 意图规划器文档,再把个人工作台 AI 模式改成“模型计划优先、规则兜底、执行器按步骤执行”的结构。
- Git 提交检查:`git fetch --all --prune` 首次被 `.git/FETCH_HEAD` 权限拦截,随后在升级权限下成功执行;当前 upstream 为 `origin/main``HEAD..@{u}``@{u}..HEAD` 均未输出新提交;工作区仍有多项既有未提交改动,本轮新增 `AI意图规划器` 文档和 planner 接线。
- 修改:新增 `document/development/AI意图规划器/CONCEPT.md``TODO.md`,明确大模型负责识别动作与步骤,业务执行器负责白名单校验、必填校验、查重和提交。
- 修改:新增 `workbenchAiIntentPlannerModel.js`,把后端 `StewardPlanResponse` 或本地 fallback 统一归一为 `intent plan`,计划中包含 `intent``requestedAction``slots``missingFields``steps`
- 修改:`useWorkbenchAiStewardFlow.js` 增加 `resolveInlineExecutionPlan()`,复用现有 `/steward/plans` 大模型 function calling 能力,返回模型计划;调用失败或超时时由前端 fallback 接管。
- 修改:`usePersonalWorkbenchAiMode.js` 不再直接消费正则识别结果,而是先尝试 steward 模型计划,再用 `buildRuleFallbackWorkbenchAiIntentPlan()` 兜底,最后通过 `resolveExecutableTravelApplicationPlan()` 驱动申请预览和自动提交;模型规划等待期间会锁住发送态,避免重复触发。
- 修改:`workbench-ai-intent-planner-model.test.mjs` 增加模型计划归一化、fallback、政策咨询排除和主流程接线测试`expense-application-fast-preview.test.mjs` 的直提断言同步为 planner 语义。
- 验证:`node --test web/tests/workbench-ai-intent-planner-model.test.mjs web/tests/workbench-ai-application-gate-model.test.mjs` 通过 8/8`node --test --test-name-pattern "direct-submit" web/tests/expense-application-fast-preview.test.mjs` 通过 2/2`npm --prefix web run build` 构建通过;`git diff --check -- ...` 无输出。
- 验证补充:整跑 `expense-application-fast-preview.test.mjs` 仍有 12 个既有失败,当前与本次 planner 新增用例无关;本轮未在真实页面输入框回放模型返回路径。
- 影响:复杂意图链路现在有了清晰的模型计划适配层;后续扩展报销、附件归集、关联申请单时,可以继续让模型输出 steps再由确定性执行器逐步执行。
- 13:35我补修了 AI 意图规划入口过窄的问题让“2026-02-20 至 2026-02-23上海出差国网仿生产服务器部署火车保存草稿。”这种轻微改写也会进入计划执行链路。
- Git 提交检查:`git fetch --all --prune` 普通沙箱首次失败,错误是 `.git/FETCH_HEAD: Operation not permitted`;随后在升级权限下成功执行。当前 upstream 为 `origin/main``HEAD..@{u}``@{u}..HEAD` 均未输出新提交;工作区仍有多项既有未提交改动,本轮只继续修改 AI 工作台意图规划、申请预览执行和对应测试。
- 修改:`workbenchAiIntentPlannerModel.js` 新增 `shouldRequestWorkbenchAiIntentPlan()`,把差旅、申请、交通方式、保存草稿、直接提交等财务执行候选先送进 steward 计划器;同时新增 `save_application_draft` step并把 `requestedAction=save_draft` 归一成可执行的 `autoSaveDraft`
- 修改:`workbenchAiApplicationGateModel.js` 放宽本地兜底识别,支持“上海出差”这种目的地前置写法和单独出现的“火车/高铁/飞机”等交通方式。
- 修改:`usePersonalWorkbenchAiMode.js` 不再只在 fallback 已经完整命中时才规划;只要 `shouldRequestWorkbenchAiIntentPlan()` 认为是财务执行候选,就先走模型计划,模型失败才用本地 fallback。
- 修改:`useWorkbenchAiApplicationPreviewFlow.js` 在生成申请核对表后支持 `autoSaveDraft`,直接复用现有 `AI_APPLICATION_ACTION_SAVE_DRAFT` 保存草稿动作;自动提交仍只在 `readyToSubmit` 为真时执行。
- 验证:`node --test web/tests/workbench-ai-intent-planner-model.test.mjs` 通过 5/5`node --test --test-name-pattern direct-submit web/tests/expense-application-fast-preview.test.mjs` 通过 2/2`node --test web/tests/workbench-ai-application-gate-model.test.mjs` 通过 4/4`npm --prefix web run build` 构建通过;`git diff --check -- ...` 无输出;尾随空白扫描无命中。
- 验证补充:用 Node 直接解析用户原句,结果为 `requestedAction=save_draft`steps 为 `build_application_preview -> validate_required_fields -> save_application_draft`,可执行参数为 `autoSubmit=false``autoSaveDraft=true`
- 影响用户一句话给日期、地点、事由、交通方式和“保存草稿”时AI 工作台会进入模型规划/兜底规划,生成申请核对表后自动保存草稿,不再因为表述顺序变化回到逐步问答。
- 13:58我按用户反馈重构了 AI 工作台意图入口,让用户话语进入后先走大模型 function calling 规划,再按模型拆出的动作和字段执行,规则只保留为模型失败后的降级。
- Git 提交检查:`git fetch --all --prune` 普通沙箱首次失败,错误是 `.git/FETCH_HEAD: Operation not permitted`;随后在升级权限下成功执行。当前 upstream 为 `origin/main``HEAD..@{u}``@{u}..HEAD` 均未输出新提交;工作区仍有多项既有未提交改动,本轮继续修改 steward function calling、AI 工作台 planner、申请预览字段接力和测试。
- 修改:后端 `StewardTask` 增加 `requested_action``StewardIntentAgent` 的 tool schema 要求模型输出 `preview/save_draft/submit``StewardModelPlanBuilder` 将模型动作写入任务,缺省时才按原文保守推断。
- 修改:`usePersonalWorkbenchAiMode.js` 将入口从“先构造 fallback 再规划”改成 `executeModelPlannedWorkbenchIntent()`:先调用 `resolveInlineExecutionPlan()`,再把模型 plan 归一为可执行申请;只有模型失败时才执行 `buildRuleFallbackWorkbenchAiIntentPlan()`,并保留模型识别到 reimbursement task 后进入原报销关联门。
- 修改:`workbenchAiIntentPlannerModel.js` 保留模型拆出的 canonical ontology fields`shouldRequestWorkbenchAiIntentPlan()` 改为普通有效输入都先进入 planner不再把政策查询等话术排除在规划入口外。
- 修改:`workbenchAiApplicationPreviewModel.js``useWorkbenchAiApplicationPreviewFlow.js` 支持把模型拆出的 `time_range/location/reason/transport_mode` 转为申请预览 ontology再生成核对表避免继续只靠本地文本解析导致地点和事由粘连。
- 测试:先让 `workbench-ai-intent-planner-model.test.mjs` 红灯失败,确认旧实现缺少 `ontologyFields`、政策查询被排除、入口仍携带 `fallbackIntentPlan`;随后实现并跑绿。
- 验证:`node --test web/tests/workbench-ai-intent-planner-model.test.mjs` 通过 7/7`node --test --test-name-pattern direct-submit web/tests/expense-application-fast-preview.test.mjs` 通过 2/2`node --test web/tests/workbench-ai-intent-planner-model.test.mjs web/tests/workbench-ai-application-gate-model.test.mjs` 通过 11/11容器 `x-financial-local-linux``pytest -q server/tests/test_steward_intent_agent.py server/tests/test_steward_planner.py` 通过 27/27容器内 `py_compile` 通过;`npm --prefix web run build` 构建通过;`git diff --check` 无输出,尾随空白扫描无命中。
- 验证补充:用 Node 直接归一模型 plan用户原句输出 `requestedAction=save_draft``ontologyFields.time_range=2026-02-20 至 2026-02-23``location=上海``reason=国网仿生产服务器部署``transport_mode=火车``autoSaveDraft=true`
- 影响AI 工作台不会再“瞬间靠规则识别并执行”;可执行链路的第一步是模型 function calling 规划,模型负责拆动作和字段,前端执行器只消费结构化 plan 并调用确定性申请/报销流程。
- 14:33我按“每次先经过大模型规划10s 超时、最多 3 次重试后才规则降级”的要求收紧了 AI 工作台意图规划链路。
- Git 提交检查:`git fetch --all --prune` 成功;当前 upstream 为 `origin/main``HEAD..@{u}``@{u}..HEAD` 均未输出新提交;工作区仍有多项既有未提交改动,本轮继续修改 steward planner/runtime、申请字段解析、AI 工作台等待时间和相关测试。
- 修改:`StewardPlannerService.build_plan()` 改成先调用模型 intent agent再根据模型结果或失败原因进入 off-topic/rule fallback不再只对多任务或显式动作走模型。
- 修改:`StewardIntentAgent` 的 function calling 调用改为 `timeout_seconds=10``max_attempts=3`,并通过 `RuntimeChatService.complete_with_tool_call(use_failure_cooldown=False)` 避免第 1 次失败后第 2、3 次被 cooldown 跳过。
- 修改:`useWorkbenchAiStewardFlow.js` 将 inline execution plan 等待时间从 12s 调到 35s覆盖后端 3 次 10s 模型等待,避免前端提前本地 fallback。
- 修改:`ApplicationFactResolver``StewardPlannerExtractionMixin` 的规则降级补齐 `requested_action`、完整日期范围和申请事由清洗;即使模型 3 次超时也能把“保存草稿”“2026-02-20 至 2026-02-23”“国网仿生产服务器部署”“火车”传给执行器。
- 测试:先新增红灯测试覆盖“单一出差描述也要先调用模型”“模型调用参数为 10s/3 次”“tool-call 规划可关闭 cooldown 连续重试”“前端等待 35s”“规则降级保留保存草稿和日期范围”随后实现并跑绿。
- 验证:容器内 `pytest -q server/tests/test_runtime_chat_service.py server/tests/test_steward_intent_agent.py server/tests/test_steward_planner.py` 通过 38/38前端 `node --test web/tests/workbench-ai-intent-planner-model.test.mjs web/tests/workbench-ai-application-gate-model.test.mjs` 通过 11/11`npm --prefix web run build` 通过;`git diff --check -- ...` 无输出。
- 真实接口验证:重启 `x-financial-local-linux` 后直接请求 5173 的 `/api/v1/steward/plans`,原句耗时 32.08sMiniMax `attempt=1/2/3` 均约 10.2s 超时后才返回 `planning_source=rule_fallback`;返回任务已包含 `requested_action=save_draft`、完整 `time_range=2026-02-20 至 2026-02-23``location=上海``reason=国网仿生产服务器部署``transport_mode=train`
- 影响:用户不会再看到毫秒级规则直出;当前模型服务超时时会先完整等待三次,再用可执行的基础规则计划继续流程。
- 15:14我把小财管家规划入口第一阶段迁到 LangGraph默认用框架接管规划编排同时保留 legacy 回退。
- Git 提交检查:`git fetch --all --prune` 成功;当前 upstream 为 `origin/main``HEAD..@{u}``@{u}..HEAD` 均未输出新提交;工作区仍有多项既有未提交改动,本轮新增 LangGraph 依赖、graph planner、runtime 开关和测试。
- 修改:新增 `StewardGraphPlannerService`,用 LangGraph `StateGraph` 编排 `prepare_context -> detect_model_intent -> off_topic/rule_fallback/完成`,不再把规划主流程继续写在一个手工 `if/else` 方法里。
- 修改:`/steward/plans``/steward/plans/stream` 的 planner 工厂默认返回 `StewardGraphPlannerService``STEWARD_AGENT_RUNTIME=legacy` 可回退到旧 `StewardPlannerService`
- 修改:`server/pyproject.toml``server/uv.lock``egg-info` 同步加入 `langgraph>=1.2,<2.0``.env.example` 增加 `STEWARD_AGENT_RUNTIME=langgraph`
- 测试:先新增 `test_steward_graph_planner.py` 红灯测试,覆盖 LangGraph planner 的模型成功路径、模型无 tool call 后规则降级、默认 LangGraph、显式启用 LangGraph 和 legacy 回退;随后实现并跑绿。
- 验证:容器内 `pytest -q server/tests/test_steward_graph_planner.py server/tests/test_steward_planner.py server/tests/test_steward_intent_agent.py server/tests/test_runtime_chat_service.py server/tests/test_config_settings_reload.py` 通过 45/45`ruff check --ignore E501 ...` 通过;`git diff --check` 无输出;`pip check` 无 broken requirements。
- 真实接口验证:通过真实 5173 `/api/v1/steward/plans` 回放保存草稿原句,服务端仍先等待 MiniMax 3 次 10s 超时,再返回 `rule_fallback`;返回字段包含 `requested_action=save_draft`、完整日期范围、上海、事由和 `transport_mode=train`。容器内直接探针确认默认 planner 类型为 `StewardGraphPlannerService`
- 影响:小财管家的规划骨架已经由 LangGraph 承接;后续可以继续把 slot/runtime decision、checkpoint 记忆、human-in-the-loop 和 action node 迁到框架里,而不是继续扩写自研分支编排。
- 15:22我按“先落文档文档完了再继续开发”的要求补齐 LangGraph 后续迁移文档。
- Git 提交检查:`git fetch --all --prune` 成功;当前 upstream 为 `origin/main``HEAD..@{u}``@{u}..HEAD` 均未输出新提交;工作区仍有多项既有未提交改动,本轮只修改 AI 意图规划器文档和当天工作日志。
- 修改:新增 `LANGGRAPH_RUNTIME_MIGRATION.md`,记录当前已完成的 LangGraph 第一阶段、目标架构、runtime state 草案、节点边界、Phase 1-5 迁移计划、回退策略、验证矩阵和风险处理。
- 修改:更新 `CONCEPT.md`,删除“非目标:不新增 LangGraph”的过期表述改为不引入 LangChain 高层 Agent、不让 LangGraph 直接接管密钥或副作用,并补充后续 node 迁移方向。
- 修改:更新 `TODO.md`,把 `requested_action`、LangGraph 接入和迁移文档标为完成并新增模型成功路径、slot/runtime node、action node、checkpoint、人审中断和 trace 的后续事项。
- 操作:查询 LangGraph 官方文档,确认 Graph API、persistence 和 interrupt 的能力边界;读取当前 `StewardGraphPlannerService` 与已有 AI 意图规划文档后再落文档。
- 验证:`rg` 确认旧“不能新增 LangGraph”说法没有残留`git diff --check -- document/development/AI意图规划器/...` 无输出;新增迁移文档 356 行。
- 影响:后续开发有了明确的执行顺序和验收标准;下一步应先按文档 Phase 1 修通真实 `llm_function_call` 成功路径,再迁 slot/runtime/action/checkpoint。
- 15:31我继续把小财管家会话内的槽位识别、运行时记忆合并和行动决策入口迁到 LangGraph并加了图内和端点级双层兜底。
- Git 提交检查:`git fetch --all --prune` 成功;当前 upstream 为 `origin/main``HEAD..@{u}``@{u}..HEAD` 均未输出新提交;工作区仍有多项既有未提交改动,本轮新增 graph runtime、runtime 测试并更新 AI 意图规划器文档。
- 修改:新增 `StewardGraphRuntime`,用 LangGraph `StateGraph` 编排 `slot_prepare_context -> slot_tool_decision -> done/rule_fallback`,把原 `StewardSlotDecisionAgent` 包成图节点;工具节点异常时会带 `langgraph_slot_decision` 失败 trace 进入规则兜底。
- 修改:`StewardGraphRuntime` 运行时图新增 `runtime_memory_context -> runtime_action_decision/runtime_tool_decision -> done/rule_fallback`,先归一化并合并 `steward_state`,已选流程确认走确定性 action 节点,不再调用模型;普通补字段/确认继续由 runtime tool 节点判断。
- 修改:`/steward/slot-decisions``/steward/runtime-decisions` 默认使用 `StewardGraphRuntime`;如果 LangGraph 实例化或执行异常,端点 helper 会退回旧 `StewardSlotDecisionAgent` / `StewardRuntimeDecisionAgent`,保证框架失效时仍能实际响应。
- 测试:先新增 `test_steward_graph_runtime.py` 红灯测试,覆盖槽位工具节点、图内规则兜底、运行时记忆合并、确定性行动选择,以及端点 helper 在 graph runtime 失败后 legacy 兜底;随后实现并跑绿。
- 文档:更新 `LANGGRAPH_RUNTIME_MIGRATION.md` 的当前状态和 Phase 2 完成项;更新 `CONCEPT.md` 的 LangGraph 节点命名;更新 `TODO.md` 勾掉 slot/runtime graph runtime 迁移,保留副作用 action node、checkpoint、人审中断和 trace 后续事项。
- 验证:容器内 `pytest -q server/tests/test_steward_graph_runtime.py server/tests/test_steward_graph_planner.py server/tests/test_steward_slot_decision_agent.py server/tests/test_steward_runtime_decision_agent.py server/tests/test_steward_planner.py server/tests/test_steward_intent_agent.py server/tests/test_runtime_chat_service.py server/tests/test_config_settings_reload.py` 通过 59/59`ruff check --ignore E501 ...` 通过;`git diff --check` 无输出;`pip check` 无 broken requirements。
- 影响:会话内“识别缺什么、读记忆、判断补字段/继续/确认”的入口已经被 LangGraph 接管;当前仍没有让 LangGraph 直接执行保存草稿、提交审批、关联申请单等副作用动作。
- 15:53我把 `/steward/plans` 的基础业务动作规划补成服务端白名单 `action_steps`,让申请、报销、保存草稿和直接提交都有明确动作序列。
- Git 提交检查:`git fetch --all --prune` 成功;当前 upstream 为 `origin/main``HEAD..@{u}``@{u}..HEAD` 均未输出新提交;工作区仍有多项既有未提交改动,本轮新增 action contract、扩展 steward schema、更新前端 planner model、修复申请事由清洗并同步文档。
- 修改:`StewardTask``StewardPlanResponse` 增加 `action_steps`;新增 `StewardActionStep``StewardActionType``StewardActionStatus`,把可执行动作显式收进 Pydantic 协议。
- 修改:新增 `StewardActionPlanBuilder`,由服务端确定性生成白名单动作:申请直接提交输出 `fill_application_fields -> build_application_preview -> validate_required_fields -> run_duplicate_precheck -> submit_application`;保存草稿输出 `save_application_draft`;报销输出 `fill_reimbursement_fields -> build_reimbursement_preview -> validate_required_fields -> create_reimbursement_draft`
- 修改:`StewardGraphPlannerService` 增加 `attach_action_steps` 节点,模型成功、模型失败兜底和 off-topic 都统一经过动作规划节点legacy `StewardPlannerService` 返回前也补同一套 action contract。
- 修改:`workbenchAiIntentPlannerModel.js` 优先消费服务端 `task.action_steps`,旧响应才回退本地 `requested_action` 推导;这样后端 LangGraph 规划出的动作能真正驱动前端执行计划。
- 修改:`ApplicationFactResolver` 修复“交通火车”污染申请事由的问题,同时保留“高铁往返”这类业务描述;新增解析回归测试。
- 测试:先新增红灯测试,覆盖申请直接提交 action steps、保存草稿 action steps、报销 action steps、前端优先消费服务端 action steps、`交通火车` 事由清洗;随后实现并跑绿。
- 验证:容器内 `pytest -q server/tests/test_application_fact_resolver.py server/tests/test_steward_graph_runtime.py server/tests/test_steward_graph_planner.py server/tests/test_steward_slot_decision_agent.py server/tests/test_steward_runtime_decision_agent.py server/tests/test_steward_planner.py server/tests/test_steward_intent_agent.py server/tests/test_runtime_chat_service.py server/tests/test_config_settings_reload.py` 通过 66/66前端 `node --test web/tests/workbench-ai-intent-planner-model.test.mjs web/tests/workbench-ai-application-gate-model.test.mjs` 通过 12/12`npm --prefix web run build` 通过;`ruff check --ignore E501 ...` 通过;`git diff --check` 无输出。
- 真实接口验证:重启 `x-financial-local-linux` 后回放 `2026-02-20 至 2026-02-23去上海出差辅助国网仿生产服务器部署交通火车直接提交`,真实 5173 `/api/v1/steward/plans` 返回 `requested_action=submit`,字段为 `time_range=2026-02-20 至 2026-02-23``location=上海``reason=辅助国网仿生产服务器部署``transport_mode=train`,动作序列为 `detect_intent:completed -> fill_application_fields:planned -> build_application_preview:planned -> validate_required_fields:planned -> run_duplicate_precheck:planned -> submit_application:pending_confirmation`
- 影响:基础业务已经不是“只识别一个 requested_action”了后端计划会直接给出可执行动作序列当前仍只规划动作不直接执行保存草稿或提交审批。
- 16:13我把小财管家的白名单 action 从“只规划”推进到“可执行 executor”让申请草稿、申请提交门禁和报销草稿能通过统一 `/steward/actions/execute` 入口处理。
- Git 提交检查:`git fetch --all --prune` 成功;当前 upstream 为 `origin/main``HEAD..@{u}``@{u}..HEAD` 均未输出新提交;工作区仍有多项既有未提交改动,本轮新增 action executor、执行接口、前端执行服务和相关测试。
- 修改:新增 `StewardActionExecuteRequest` / `StewardActionExecuteResponse`,并在 `steward.py` 暴露 `/steward/actions/execute`;新增 `StewardActionExecutor`,统一处理未知 action 拒绝、缺字段阻断、提交确认门禁、precheck 阻断和业务服务调用。
- 修改:`save_application_draft` / `submit_application` 复用现有 `UserAgentService` 申请保存/提交能力;`create_reimbursement_draft` 复用 `ExpenseClaimService.save_or_submit_from_ontology()`,避免再写一套入库逻辑。
- 修改:`associate_attachments``link_existing_application` 保留在执行白名单里,但在真实接线完成前统一返回阻断,不允许以 noop 形式“假成功”或产生隐式副作用。
- 修改:前端 `steward.js` 新增 `executeStewardAction()``stewardPlanModel.js` 保留服务端 `action_steps`,并在 suggested action payload 中携带最终可执行 action step旧申请预览流继续保留为兜底。
- 修改:`useWorkbenchAiActionRouter.js` 接入 `steward_execute_action` 点击执行;申请直接提交会先调用 `run_duplicate_precheck`precheck 通过后再把结果交给 `submit_application`,执行结果回写到当前 AI 会话。
- 修改:`test_reimbursement_endpoints.py` 两个申请快路径断言从旧 `AP-` 前缀同步到当前短单号 `A...` 规范。
- 文档:更新 `LANGGRAPH_RUNTIME_MIGRATION.md``TODO.md``CONCEPT.md`,标记第一版 action registry / executor 完成,并把 checkpoint、interrupt、关联申请和附件真实执行列为后续。
- 验证:容器内 `pytest -q server/tests/test_steward_action_executor.py` 通过 5/5迁移集合 `pytest -q server/tests/test_application_fact_resolver.py server/tests/test_steward_action_executor.py server/tests/test_steward_graph_runtime.py server/tests/test_steward_graph_planner.py server/tests/test_steward_slot_decision_agent.py server/tests/test_steward_runtime_decision_agent.py server/tests/test_steward_planner.py server/tests/test_steward_intent_agent.py server/tests/test_runtime_chat_service.py server/tests/test_config_settings_reload.py` 通过 71/71。
- 验证:前端 `node --test web/tests/workbench-ai-action-router.test.mjs web/tests/steward-actions-service.test.mjs web/tests/steward-plan-message-copy.test.mjs web/tests/workbench-ai-intent-planner-model.test.mjs web/tests/workbench-ai-application-gate-model.test.mjs` 通过 21/21`npm --prefix web run build` 通过;`ruff check --ignore E501 ...` 通过;`git diff --check` 无输出。
- 真实接口验证:重启 `x-financial-local-linux` 后,真实 5173 `/api/v1/steward/plans` 仍返回直接提交 action 链;`/api/v1/steward/actions/execute` 对未确认的 `submit_application` 返回 `needs_confirmation`,没有执行入库副作用。
- 影响:基础业务链路已经具备“规划 action -> 执行器门禁 -> 复用现有业务服务”的后端闭环;下一步需要把 executor 包进 LangGraph checkpoint / interrupt并补齐关联申请和附件关联的真实执行。
- 16:40我把 action executor 包成 LangGraph 可执行节点,并补完 checkpoint、人工确认中断、幂等重放、关联申请单和附件关联三阶段收尾。
- Git 提交检查:`git fetch --all --prune` 成功;当前 upstream 为 `origin/main``HEAD..@{u}``@{u}..HEAD` 均未输出新提交;工作区仍有多项既有未提交改动,本轮新增 `StewardGraphActionRuntime`,扩展 action executor 测试并更新 AI 意图规划器文档。
- 修改:新增 `StewardGraphActionRuntime`,用 LangGraph `StateGraph` 编排 `action_checkpoint_load -> action_execute_node -> action_checkpoint_persist`,并把 `/steward/actions/execute` 默认切到这个 graph runtime。
- 修改:复用 `AgentConversation.state_json` 持久化 `steward_action_checkpoint`;同一 `conversation_id + client_trace_id` 的已完成 action 会直接重放终态结果,`needs_confirmation` 会保存 pending interrupt后续同 trace 完成后清理对应中断。
- 修改:`link_existing_application` 走报销草稿真实创建链路并写入申请关联风险标记;`associate_attachments` 接入 `AttachmentAssociationJobRunner`,无 `receipt_ids` 时阻断,避免假成功。
- 修改:为 `complete_with_tool_call()` 补上主模型失败后 backup 返回 tool call 的回归测试,锁住“模型规划失败优先重试/切 backup再降级规则”的成功路径。
- 文档:更新 `LANGGRAPH_RUNTIME_MIGRATION.md``TODO.md``CONCEPT.md`,把 action node、checkpoint、pending interrupt、幂等重放、关联申请单和附件关联标为已完成同时保留真实模型配置未修通的验收缺口。
- 验证:容器内 `pytest -q server/tests/test_steward_action_executor.py` 通过 9/9`pytest -q server/tests/test_runtime_chat_service.py::test_runtime_chat_complete_with_tool_call_fails_over_to_backup_before_retrying_main` 通过 1/1。
- 验证:迁移集合 `pytest -q server/tests/test_application_fact_resolver.py server/tests/test_steward_action_executor.py server/tests/test_steward_graph_runtime.py server/tests/test_steward_graph_planner.py server/tests/test_steward_slot_decision_agent.py server/tests/test_steward_runtime_decision_agent.py server/tests/test_steward_planner.py server/tests/test_steward_intent_agent.py server/tests/test_runtime_chat_service.py server/tests/test_config_settings_reload.py` 通过 76/76附件/关联任务 `pytest -q server/tests/test_attachment_association_jobs.py server/tests/test_linked_reimbursement_draft_jobs.py` 通过 7/7。
- 验证:前端 `node --test web/tests/workbench-ai-action-router.test.mjs web/tests/steward-actions-service.test.mjs web/tests/steward-plan-message-copy.test.mjs web/tests/workbench-ai-intent-planner-model.test.mjs web/tests/workbench-ai-application-gate-model.test.mjs` 通过 21/21`ruff check --ignore E501 ...` 通过;`npm --prefix web run build` 通过;`git diff --check` 无输出。
- 真实接口验证:重启 `x-financial-local-linux` 后,真实 5173 `/steward/actions/execute` 对同一未确认 `submit_application` trace 第二次返回 `needs_confirmation``result_payload.idempotent_replay=true`,证明 checkpoint 重放生效。
- 真实接口验证:真实 5173 `/steward/plans` 回放“2026-02-20 至 2026-02-23上海出差国网仿生产服务器部署火车保存草稿”仍因 backup skipped、MiniMax 三次失败后进入 `rule_fallback`,动作链和字段可用,但 `planning_source=llm_function_call` 仍需修模型配置。
- 影响申请、报销、保存草稿、直接提交、关联申请单、附件关联已经具备“LangGraph 规划/行动节点 -> 业务服务执行 -> checkpoint/interrupt 兜底”的基础可测闭环;真实大模型成功路径还差外部模型连通性。
- 16:53我修复 AI 模式输入后按回车“看起来没响应”的问题,把模型规划等待期改成先显示对话反馈。
- Git 提交检查:`git fetch --all --prune` 成功;当前 upstream 为 `origin/main``HEAD..@{u}``@{u}..HEAD` 均未输出新提交;工作区仍有多项既有未提交改动,本轮只改 AI 模式前端等待反馈和对应测试/日志。
- 根因:`executeModelPlannedWorkbenchIntent()` 在等待 `/steward/plans` 返回前只设置 `sending=true`,没有先激活对话、清空输入框、展示用户气泡或 pending 卡片;真实 MiniMax 三次超时会让用户看到 30 多秒“无响应”。
- 修改:`usePersonalWorkbenchAiMode.js` 新增 `startModelPlanningConversation()`,进入模型规划前立即推入用户消息和“正在识别意图,准备拆解申请、报销和附件任务。”的 pending 卡片,并持久化到最近对话。
- 修改:`startModelPlannedApplicationPreview()` 把规划 pending 的 `pendingMessageId` 传给申请预览;`useWorkbenchAiApplicationPreviewFlow.js` 支持复用这张 pending 卡片,避免重复用户消息和重复等待卡。
- 测试:先新增 `workbench AI mode shows a visible planning response before waiting for steward model plan` 红灯测试,确认当前缺少即时反馈;实现后同文件 9/9 通过。
- 验证:前端相关集合 `node --test web/tests/workbench-ai-action-router.test.mjs web/tests/steward-actions-service.test.mjs web/tests/steward-plan-message-copy.test.mjs web/tests/workbench-ai-intent-planner-model.test.mjs web/tests/workbench-ai-application-gate-model.test.mjs web/tests/workbench-ai-composer-components.test.mjs` 通过 31/31`npm --prefix web run build` 通过;`git diff --check` 无输出。
- 真页验证:使用本地 5173 和本地模拟员工账号登录 AI 模式输入“2026-02-20 至 2026-02-23上海出差国网仿生产服务器部署火车保存草稿”并按回车1.5 秒内看到用户气泡、输入框清空和 pending 卡片;约 38 秒后仍正常保存草稿,草稿单号 `AZ8QSX9QA`
- 影响:用户按回车后不再空等模型规划,长耗时仍存在但前端会立即反馈“已接收并正在规划”;真实 `llm_function_call` 成功路径仍依赖模型配置修复。
- 16:59我修复了 AI 模式普通问候被拆成两条助手消息的问题,把规划、工具/行动整理和最终回复收敛到同一张助手卡片。
- Git 提交检查:`git fetch --all --prune` 成功;当前 upstream 为 `origin/main``HEAD..@{u}``@{u}..HEAD` 均未输出新提交;工作区仍有多项既有未提交改动,本轮只继续修改 AI 模式前端消息复用、思考事件合并和对应测试。
- 根因16:53 的等待态修复先创建了模型规划 pending 卡片,但普通管家回复路径会把这张卡片替换成“已完成意图识别,继续为您整理回复。”,随后 `requestInlineAssistantReply()` 又新建第二张助手卡片;所以用户输入“你好”时会看到两段会话。
- 修改:`usePersonalWorkbenchAiMode.js` 删除中间态独立回复,普通管家回复也把 `pendingMessageId` 传给 `requestInlineAssistantReply()`,让同一轮输入继续复用规划 pending 卡片。
- 修改:`useWorkbenchAiStewardFlow.js` 支持 `requestInlineAssistantReply(prompt, entry, files, options)`,当传入 `pendingMessageId` 时替换原 pending 卡片而不是追加新消息;新增 `mergeInlineThinkingEvents()` 合并模型规划和管家回复思考事件,避免同一事件重复或丢失。
- 测试:先新增红灯测试 `workbench AI mode reuses planning pending message for regular steward replies`,确认普通回复必须复用规划 pending实现后同文件 10/10 通过。
- 验证:前端集合 `node --test web/tests/workbench-ai-action-router.test.mjs web/tests/steward-actions-service.test.mjs web/tests/steward-plan-message-copy.test.mjs web/tests/workbench-ai-intent-planner-model.test.mjs web/tests/workbench-ai-application-gate-model.test.mjs web/tests/workbench-ai-composer-components.test.mjs` 通过 32/32`npm --prefix web run build` 通过;`git diff --check` 无输出。
- 真页验证:本地 5173 AI 模式输入“你好”后1.5 秒内只有 1 张 pending 助手卡;最终回复后 `assistantCardCount=1``pendingCards=0`、思考区标题为“小财业务思考5 条”,截图保存为 `/tmp/x-financial-ai-single-message-hello.png`
- 影响:一次用户输入现在对应一条助手消息;意图识别、规划等待、普通回复整理都会落在同一个思考区域,不再把“已完成意图识别”单独展示成一轮会话。
- 17:26我把 AI 模式的规划等待态从静态“思考中”改成持续追加过程摘要,让用户能看到意图判断、字段抽取、动作规划和兜底策略正在推进。
- Git 提交检查:`git fetch --all --prune` 成功;当前 upstream 为 `origin/main``HEAD..@{u}``@{u}..HEAD` 均未输出新提交;工作区仍有多项既有未提交改动,本轮只继续修改 AI 模式前端 thinking 事件、流式规划接线和对应测试。
- 根因:`executeModelPlannedWorkbenchIntent()` 虽然会先创建 pending 卡片,但 `resolveInlineExecutionPlan()` 仍走非流式 `/steward/plans`,模型等待期间只有一条静态 thinking等计划返回后才一次性显示完整步骤用户会感觉像假思考。
- 修改:新增 `workbenchAiPlanningThinkingModel.js`,集中维护可复用的 thinking 事件合并、完成态转换、初始意图判断和阶段性进度计划,避免继续把主 composable 写大。
- 修改:`usePersonalWorkbenchAiMode.js` 在模型规划 pending 卡片上启动可取消的阶段性进度:判断办理意图、抽取关键信息、规划执行步骤、匹配业务工具、准备兜底策略;模型返回或进入 fallback 后会清理定时器。
- 修改:`useWorkbenchAiStewardFlow.js` 支持 `resolveInlineExecutionPlan(prompt, entry, files, { pendingMessageId })`;传入 pending id 时改走 `/steward/plans/stream`,只把服务端 thinking 实时写回当前卡片,不把 answer delta 提前塞进规划消息。
- 修改:`useWorkbenchAiApplicationPreviewFlow.js` 生成申请核对表时保留并完成前置规划 thinking再追加“整理申请表字段 / 同步费用测算”,避免申请表阶段把前面的规划过程清掉。
- 测试:新增 `workbench AI mode streams planning thinking into the pending message`,锁定阶段性 thinking、可取消定时器、流式规划接线和申请预览 thinking 合并;同文件 11/11 通过。
- 验证:相关前端集合 `node --test web/tests/workbench-ai-action-router.test.mjs web/tests/steward-actions-service.test.mjs web/tests/steward-plan-message-copy.test.mjs web/tests/workbench-ai-intent-planner-model.test.mjs web/tests/workbench-ai-application-gate-model.test.mjs web/tests/workbench-ai-composer-components.test.mjs` 通过 33/33`npm --prefix web run build` 通过;`git diff --check` 无输出。
- 真页验证:本地 5173 干净会话输入“2026-02-20 至 2026-02-23上海出差国网仿生产服务器部署火车保存草稿。”后thinking 数量按时间增长:点击后 2 条、1.1 秒 3 条、2.5 秒 4 条、5.7 秒 5 条、9.6 秒 6 条;约 36 秒后仍成功保存草稿,草稿单号 `AZ4A98BSF`,截图保存为 `/tmp/x-financial-ai-progressive-thinking.png`
- 验证补充:我额外跑了 `workbench-ai-mode-switch.test.mjs`,该文件仍有 2 个既有静态断言失败,断言点还停在旧的内联附件卡片和旧的 `selectedFiles` watch 结构;本轮相关测试和真页回放不受影响。
- 影响:用户现在不再只看到静态“思考中”,而是能在同一张思考卡片里持续看到业务意图、槽位、动作链和兜底策略的推进过程;服务端流式 thinking 到达后也会合并进同一个列表。
- 17:40我修复了 AI 工作台保存申请草稿后,再输入“提交这个单据”会重新进入意图规划的问题。
- Git 提交检查:`git fetch --all --prune` 成功;当前 upstream 为 `origin/main``HEAD..@{u}``@{u}..HEAD` 均未输出新提交;工作区仍有大量既有未提交改动,本轮只补 AI 工作台申请上下文提交识别、申请预览执行分支和对应测试。
- 根因:`resolveInlineApplicationPreviewTextAction()` 只识别“提交 / 确认提交 / 直接提交”等精确词,用户输入“提交这个单据”会绕过当前申请核对表上下文,继续进入 `shouldRequestWorkbenchAiIntentPlan()` 和小财管家规划,导致再次识别意图并等待模型。
- 修改:`workbenchAiApplicationGateModel.js` 把“提交这个单据 / 提交这个申请单 / 提交当前单据 / 提交刚才的草稿”等上下文指代短句映射为 `AI_APPLICATION_ACTION_SUBMIT`;同类保存指代短句也映射到保存草稿动作。
- 修改:`useWorkbenchAiApplicationPreviewFlow.js` 在提交动作里新增“已保存草稿 + 上下文提交短句”快路径;当最近申请核对表已有 `draftPayload.claim_id` 或单号,且用户明确说提交当前/刚才的单据时,跳过二次确认弹窗,直接执行提交前核查和 `/reimbursements/application-preview-action`,并带上 `application_edit_claim_id` 复用原草稿。
- 测试:先新增 `workbench-ai-application-context-submit.test.mjs` 红灯测试,复现“已有草稿 + 提交这个单据”只打开确认弹窗、不调用提交接口;实现后该测试通过,并确认 payload 写入 `application_edit_claim_id=claim-saved-draft`
- 验证:`node --test web/tests/workbench-ai-application-context-submit.test.mjs` 通过;`node --test web/tests/workbench-ai-application-gate-model.test.mjs` 通过;相邻 `workbench-ai-action-router.test.mjs``workbench-ai-intent-planner-model.test.mjs``ai-application-preview-actions.test.mjs` 均通过;本轮改动文件 `git diff --check -- ...` 无输出。
- 验证补充:`workbench-ai-mode-expense-scene-action.test.mjs``expense-application-fast-preview.test.mjs` 仍有既有静态断言失败,主要还在扫描旧 `PersonalWorkbenchAiMode.vue` 或过窄源码片段;新增的上下文提交行为测试已覆盖真实执行路径。
- 影响用户先说“2026-02-20 至 2026-02-23去上海出差辅助国网仿生产服务器部署交通火车保存草稿”草稿保存后再说“提交这个单据”前端不会再重新规划而会把“这个单据”绑定到最近申请草稿并提交到审批链路。
- 17:48我修复了“完整出差信息但未显式说申请/报销”时AI 模式停在候选流程确认、不直接推进申请预览的问题。
- Git 提交检查:`git fetch --all --prune` 成功;当前 upstream 为 `origin/main``HEAD..@{u}``@{u}..HEAD` 均未输出新提交;工作区仍有大量既有未提交改动,本轮只继续修改 `workbenchAiIntentPlannerModel.js` 和对应意图规划测试。
- 根因:后端已经返回 `pending_flow_confirmation`,且候选流只有 `travel_application / 先发起出差申请`,但前端 `normalizeWorkbenchAiIntentPlan()` 只识别 `tasks`,没有把这个唯一候选流转成可执行申请计划,最后又回到普通 steward 回复路径。
- 修改:`workbenchAiIntentPlannerModel.js` 增加 pending flow 候选解析;当 `pending_flow_confirmation.status=pending` 且唯一候选为“先发起出差申请”时,直接生成 `create_travel_application` 计划,包含 `build_application_preview`、必填校验、模型抽取的时间/地点/事由/交通方式。
- 测试:先新增红灯用例 `workbench AI intent planner turns single application candidate flow into executable preview payload`,复现该计划原本返回 `null`;实现后同文件 12/12 通过。
- 验证:`node --test web/tests/workbench-ai-intent-planner-model.test.mjs``node --test web/tests/workbench-ai-application-gate-model.test.mjs``node --test web/tests/workbench-ai-application-context-submit.test.mjs``node --test web/tests/steward-plan-model-pending-flow.test.mjs` 均通过;`git diff --check -- web/src/composables/workbenchAiMode/workbenchAiIntentPlannerModel.js web/tests/workbench-ai-intent-planner-model.test.mjs` 无输出。
- 影响用户输入“2026-02-20 至 2026-02-23去上海出差辅助国网仿生产服务器部署交通火车”时如果门禁已查明没有可关联申请单前端会把“应先申请单据”直接落到申请预览链路不再表现成只会反复识别意图。
- 22:04我修复了 AI 工作台短句意图漏识别,覆盖“删除草稿”和“我要审核/待办审批”这两类高频输入。
- Git 提交检查:`git fetch --all --prune` 成功;当前 upstream 为 `origin/main``HEAD..@{u}``@{u}..HEAD` 均未输出新提交;本轮修改集中在 AI 工作台命令意图、待审单据查询识别和对应前端测试。
- 根因:工作台输入链路目前优先进入模型规划,但前端本地可执行意图只覆盖申请预览、报销和单据查询的较完整问法;“我要审核”没有命中 `resolveAiDocumentQueryIntent()` 的待审查询触发词,“删除草稿”则没有上下文命令入口,容易退回普通 steward 规划或被当作报销填槽文本。
- 修改:新增 `workbenchAiCommandIntentModel.js``useWorkbenchAiCommandIntents.js`,把“删除草稿 / 删除这个申请单 / 把刚才保存的草稿删掉”等短句识别为上下文命令;命中最近草稿时只给“查看草稿详情”动作,要求用户在详情页二次确认删除,不直接执行破坏性删除。
- 修改:`usePersonalWorkbenchAiMode.js` 接入命令意图 composable并让删除草稿意图优先于报销草稿填槽处理未找到最近草稿时自动转为“我的草稿单据”查询帮助用户先选中目标。
- 修改:`aiDocumentQueryIntent.js` 扩展“我要审核 / 我要审批 / 待办审批 / 去审核 / 处理审批”等短句到待我审核单据查询,同时排除“审批规则怎么走”这类政策咨询,避免误打到单据列表。
- 测试:先补红灯测试复现缺口;实现后 `node --test web/tests/workbench-ai-command-intent-model.test.mjs web/tests/ai-document-query-model.test.mjs` 通过 19/19相邻 `workbench-ai-action-router.test.mjs``workbench-ai-intent-planner-model.test.mjs` 通过;`npm --prefix web run build` 通过;`git diff --check -- ...` 无输出。
- 真页烟测:当前 `x-financial-local-linux` 正在监听 5173`curl -I http://127.0.0.1:5173/app/workbench` 返回 200说明本地 AI 工作台入口可访问。
- 影响:用户在 AI 工作台直接输入“删除草稿”时,会被引导到最近草稿详情并保留删除确认边界;输入“我要审核”或“待办审批”时,会直接查询待我审核单据,不再表现为没识别到意图。
- 22:31我把 AI 工作台意图识别从场景补丁收敛成统一 Intent Frame 方案,并按方案完成第一版前端接线。
- Git 提交检查:`git fetch --all --prune` 成功;当前 upstream 为 `origin/main``HEAD..@{u}``@{u}..HEAD` 均未输出新提交;本轮在 22:04 短句修复基础上继续新增 Markdown 方案、统一意图框架、动作策略、筛选能力和前端测试。
- 文档:新增 `2026-06-24-workbench-intent-frame-design.md`,明确 `IntentFrame Parser -> Target Resolver -> Action Policy -> 业务 flow` 的总体方案;用户确认只需要 `.md` 后,我清理了临时 DOCX 产物,只保留 Markdown 文档。
- 修改:新增 `workbenchIntentFrameModel.js`,把输入统一解析为 `action / objectType / filters / targetMode / safetyLevel / normalizedQuery`;覆盖“删除刚才那个草稿”“删除 3 天前的草稿”“审核合规没有风险的申请”和“审批规则怎么走”。
- 修改:新增 `workbenchIntentActionPolicy.js`,把删除、审核、驳回固定为 `confirm_required`;当前上下文目标只打开详情确认,筛选型目标只查询候选,不通过自然语言直接执行副作用。
- 修改:`useWorkbenchAiCommandIntents.js` 先走统一 Intent Frame再分发到最近草稿详情确认或候选查询并排除“删除附件/票据”这类非单据删除,避免抢走附件流程。
- 修改:`aiDocumentQueryIntent.js``aiDocumentQueryModel.js` 增加“3 天前”相对日期、无风险/高/中/低风险筛选、`我的草稿单据` 查询来源识别和风险摘要展示;“审批规则怎么走”仍保持规则咨询,不进入单据查询。
- 测试:先补红灯测试复现缺口;实现后 `node --test web/tests/workbench-intent-frame-model.test.mjs` 通过 4/4`node --test web/tests/ai-document-query-model.test.mjs` 通过 16/16相邻 `workbench-ai-command-intent-model.test.mjs``workbench-ai-action-router.test.mjs``workbench-ai-intent-planner-model.test.mjs` 均通过。
- 验证:`npm --prefix web run build` 通过,保留既有 Rollup pure 注释与 chunk size warning`git diff --check -- ...` 无输出;`curl -I http://127.0.0.1:5173/app/workbench` 返回 200。
- 影响:后续“删除 3 天前的草稿”会先转成“我的 3天前 草稿单据”候选查询;“审核合规没有风险的申请”会先转成“待我审核 无风险 申请单”候选查询;“刚才那个草稿”仍可在当前会话内定位最近草稿,但只给详情确认入口。
- 22:36我修复了“删除申请单草稿”虽然识别到删除动作但前端没有显示删除思考过程的问题。
- Git 提交检查:`git fetch --all --prune` 成功;当前 upstream 为 `origin/main``HEAD..@{u}``@{u}..HEAD` 均未输出新提交;本轮继续修改统一 Intent Frame 查询句、单据查询 thinking 和对应回归测试。
- 根因:`resolveWorkbenchIntentFrame()` 能解析出 `action=delete`,但 `normalizedQuery` 被压成“我的草稿单据”,丢了“申请单”筛选;同时 `handleAiDocumentQueryIntent()` 只接收查询句,不接收原始 `commandFrame`,导致 thinking 只展示“解析筛选条件”,没有展示“删除是高风险动作,不会直接执行,会先筛选候选”。
- 修改:`workbenchIntentFrameModel.js` 保留“草稿 + 申请单”的组合筛选,`删除申请单草稿` 现在生成 `我的 草稿 申请单`,避免查询阶段退化成全部草稿。
- 修改:`aiDocumentQueryModel.js` 新增 `buildAiDocumentQueryThinkingEvents()`,当传入 `commandFrame``safetyLevel=confirm_required` 时,首个 thinking 事件固定为“识别高风险操作意图”,文案明确删除/审核不会直接执行,会先筛选候选。
- 修改:`useWorkbenchAiDocumentQueryFlow.js` 支持第三个参数 `{ commandFrame }``useWorkbenchAiCommandIntents.js` 在候选查询时传入统一意图帧,让查询 thinking 保留原始动作语义。
- 测试:新增回归覆盖 `删除申请单草稿`,断言查询句保留 `申请单``草稿`,且 thinking 首条包含“删除 / 不会直接执行 / 先筛选候选”;相关 `workbench-intent-frame-model.test.mjs` 6/6 通过。
- 验证:`node --test web/tests/workbench-intent-frame-model.test.mjs web/tests/workbench-ai-command-intent-model.test.mjs web/tests/ai-document-query-model.test.mjs` 通过 27/27相邻 `workbench-ai-action-router.test.mjs``workbench-ai-intent-planner-model.test.mjs` 通过 17/17`npm --prefix web run build` 通过;`git diff --check -- ...` 无输出5173 工作台路由返回 200。
- 影响:用户输入“删除申请单草稿”时,不会只看到普通查询筛选;系统会先展示删除动作的高风险策略思考,再查询“我的草稿申请单”候选,并保留详情页二次确认边界。
- 22:42我补强了高风险命令的最终候选结果提示让用户在卡片结果里也能看到“不会直接删除/审核”和快捷入口说明。
- Git 提交检查:`git fetch --all --prune` 成功;当前 upstream 为 `origin/main``HEAD..@{u}``@{u}..HEAD` 均未输出新提交;本轮继续修改 `aiDocumentQueryModel.js`、查询 flow 和统一意图回归测试。
- 根因22:36 只把删除动作写进 thinking 事件,但最终候选结果仍是普通单据卡片和“查看详情”按钮;用户如果只看结果卡片,会看不到“系统不会直接删除,请先进入详情确认”的明确操作边界。
- 修改:`buildAiDocumentQueryMessage()` 支持 `commandFrame`,当命令为删除/审核/驳回等 `confirm_required` 动作时,在候选卡片上方插入高风险操作提示:系统不会直接删除/审核相关单据,需点击下方候选单据里的快捷按钮并进入详情核对后再操作。
- 修改:候选单据卡片的操作按钮会随命令动作改文案:删除为“进入详情确认删除”,审核为“进入详情确认审核”,驳回为“进入详情确认驳回”;普通查询仍保持“查看详情”。
- 修改:`useWorkbenchAiDocumentQueryFlow.js` 在生成最终查询消息时传入 `commandFrame`,保证真实工作台路径能展示同一套提示和按钮文案。
- 测试:新增回归断言 `删除申请单草稿` 的结果消息包含“系统不会直接删除相关单据 / 请点击下方候选单据里的快捷按钮 / 进入单据详情核对后再操作”,并出现 `进入详情确认删除` 按钮文案。
- 验证:`node --test web/tests/workbench-intent-frame-model.test.mjs web/tests/workbench-ai-command-intent-model.test.mjs web/tests/ai-document-query-model.test.mjs` 通过 28/28相邻 `workbench-ai-action-router.test.mjs``workbench-ai-intent-planner-model.test.mjs` 通过 17/17`npm --prefix web run build` 通过;`git diff --check -- ...` 无输出5173 工作台路由返回 200。
- 影响:用户输入“删除申请单草稿”后,既能在 thinking 里看到删除动作识别,也能在最终结果卡片里看到删除确认边界和实体快捷入口,不会误以为系统已经直接删除或只能普通查看。
- 22:49我根据截图修复了“删除申请单草稿”结果页只剩“已查询到相关单据”、高风险提示和候选卡片没有显示的问题。
- Git 提交检查:`git fetch --all --prune` 成功;当前 upstream 为 `origin/main``HEAD..@{u}``@{u}..HEAD` 均未输出新提交;工作区仍是本轮 AI 工作台意图识别和结果渲染相关未提交改动。
- 根因:高风险提示块里使用了 `<p>` 标签,但 `conversationTrustedHtml` 的 trusted HTML 白名单不允许 `<p>`;安全渲染会把包含该标签的整段 trusted HTML 丢弃,所以页面只保留 Markdown 标题,看起来像没有继续删除思考。
- 修改:`aiDocumentQueryModel.js` 将高风险提示正文改为白名单已允许的 `<div>`,不扩大 trusted HTML 标签面;`personal-workbench-ai-mode.css` 增加提示块样式,让“系统不会直接删除相关单据”和后续详情确认说明稳定显示在候选卡片上方。
- 测试:先新增渲染级红灯测试复现截图,确认渲染后只剩标题;修复后该测试断言 `ai-document-command-guidance``系统不会直接删除相关单据``进入详情确认删除``ai-document-card-list` 都保留在最终 HTML 中。
- 验证:`node --test web/tests/ai-document-query-model.test.mjs` 通过 17/17`node --test web/tests/ai-document-query-model.test.mjs web/tests/workbench-intent-frame-model.test.mjs web/tests/workbench-ai-command-intent-model.test.mjs web/tests/workbench-ai-action-router.test.mjs web/tests/workbench-ai-intent-planner-model.test.mjs` 通过 46/46`npm --prefix web run build` 通过;`git diff --check -- ...` 无输出;`curl -I http://127.0.0.1:5173/app/workbench` 返回 200。
- 影响:用户输入“删除申请单草稿”后,结果标题下方会恢复高风险说明、候选单据卡片和“进入详情确认删除”快捷按钮;系统仍不会直接删除单据,而是要求进入详情核对后再操作。
## 遗留问题
- 22:49本轮用渲染级回归测试确认 trusted HTML 已恢复,但还没有做真实浏览器点击级回放和截图复核。建议后续在 5173 输入“删除申请单草稿”,确认真实主题下提示块、候选卡片和按钮没有拥挤或错位。
- 22:42本轮仍未做真实浏览器点击级回放确认高风险提示在 AI 工作台卡片里的视觉效果。建议后续在 5173 输入“删除申请单草稿”,截图确认提示块、候选卡片和“进入详情确认删除”按钮在当前主题下没有拥挤或错位。
- 22:36本轮仍未做浏览器点击级回放确认“删除申请单草稿”的实际 thinking 卡片视觉状态。建议后续在真实 5173 工作台输入该句,确认首条 thinking 为删除高风险策略、后续才是查询和筛选候选。
- 22:31本轮已完成统一意图框架的模型测试、查询筛选测试、相邻前端测试、构建和 5173 路由烟测,但没有做浏览器点击级回放确认“删除 3 天前的草稿 / 审核合规没有风险的申请”的最终候选卡片。建议后续在真实工作台输入这两句,截图确认 thinking、候选列表和详情入口。
- 22:04本轮完成了前端行为测试、构建和 5173 路由烟测,但没有做浏览器点击级回放确认输入“删除草稿 / 我要审核”的最终卡片状态。建议后续在真实浏览器里回放这两个短句,截图确认动作卡片和待审单据列表符合预期。
- 09:41当前 Skill 是新建在项目级 `.codex/skills` 目录里,本轮可以通过文件检查验证结构,但是否被未来会话自动加载还依赖 Codex 对项目 Skill 的刷新机制。建议后续新开会话或下一次任务时确认 Skill 列表是否出现 `agent-change-log`
- 09:43`.codex/` 曾被 `.gitignore` 整体忽略,新建的 `agent-change-log` 默认不会进入普通提交范围09:44 已改成只放行该 Skill。建议后续如果还新增其他项目 Skill也按同样方式逐个显式放行别一次性开放整个 `.codex`
- 10:01当前工作区已有大量未提交改动且本地 `main` ahead 1。建议后续如果真的发现 upstream 新提交,先用 fetch 和 `HEAD..@{u}` 写日志;只有在工作区干净、可快进时再执行 `git pull --ff-only`,避免把其他智能体提交和本地半成品混到一起。
- 10:02本地 ahead 提交也可能来自其他智能体,不能只看 upstream behind。建议后续日志固定同时记录 `HEAD..@{u}``@{u}..HEAD` 两个方向。
- 10:21自动日志触发时发现 fetch 未成功失败error: cannot open '.git/FETCH_HEAD': Operation not permitted。建议后续在有 Git 写权限和网络权限的环境里重新执行拉取检查。
- 10:21当前环境不能写 `.git/hooks`,所以 post-commit hook 模板已经入库,但尚未安装到本 checkout。建议在有 `.git` 写权限的环境执行 `tools/agent-change-log/install_post_commit_hook.sh`
- 10:24本轮未能在容器内执行 pytest也未能实际确认容器是否已安装 `mupdf-tools`。建议在 Docker 权限恢复后重建/重启 `local-x-financial-linux`,执行定向 OCR 测试,并在 5173 用真实 PDF 票据确认预览图和 OCR 字段都保留中文。
- 10:2810:06 那条既有票据记录已经写成 PDF fallback代码修复不会自动改写旧 meta。建议重新上传一次同一 PDF或在容器权限恢复后触发票据重识别/重建预览,确认新记录变为 PNG 预览。
- 10:32最新 10:29 上传仍然写成 PDF说明运行中的后端可能还没加载最新代码或本轮上传发生在缓存版本修复之前。建议重启后端/重建容器后再重新上传,确认 OCR cache key 已包含 `pdf-image-ocr:`
- 10:40本轮可以确认 5173 服务可达、结构测试和生产构建通过,但当前环境没有可调用的浏览器自动化插件,项目也没有现成 Playwright/Puppeteer 依赖,所以未生成真实页面截图。建议在具备浏览器自动化的环境回放一次附件预览,确认主内容区居中效果。
- 11:06缓存管理 UI 本轮已完成结构测试、构建和 5173 路由可达性检查,但没有真实浏览器截图证据。建议后续在本机浏览器打开系统设置 / 缓存管理,确认视觉密度和按钮位置是否符合预期。
- 10:41票据夹 PDF 保存阶段已补主动生成图片预览,但当前沙箱不能访问 Docker socket无法运行容器内 pytest也无法刷新已有 PDF 票据的旧 meta。建议 Docker 权限恢复后先跑定向测试,再对旧记录触发 `resolve_preview` 或重识别来补 `preview.png`
- 10:56系统缓存清理接口已通过语法检查和前端构建但后端容器定向 pytest 仍被 Docker socket 权限挡住。建议 Docker 权限恢复后运行 `server/tests/test_system_cache_endpoints.py`,并在 5173 系统设置页实际点击一次“一键清理缓存”确认接口返回明细。
- 10:57用户最新测试仍看到 PDF 预览,当前证据指向运行中的后端未重启加载新代码,且容器内 PDF 渲染工具状态无法由本沙箱确认。建议在有 Docker 权限的终端重建/重启 `local-x-financial-linux` 或当前 compose main 服务,确认 `mutool` / `pdftoppm` 可用后重新打开票据预览。
- 11:09`POST /api/v1/settings/cache/clear` 的 404 已确认来自运行中 FastAPI 未加载新路由;当前沙箱不能通过 Docker socket 或 SSH 进入容器重启。建议在有 Docker 权限的终端重启/重建 `local-x-financial-linux`,然后检查 `/api/v1/openapi.json` 是否包含 `/api/v1/settings/cache/clear`
- 11:15高级 UI 视觉重构暂无本地真实页面渲染的自动化截图,且 11:19 已按截图反馈收敛回简洁风格。建议后续以真实浏览器截图为准继续细调。
- 11:20当前 Codex 沙箱仍无法访问 Docker socket不能直接替用户重启 `local-x-financial-linux` 或运行容器内 pytest。建议在有 Docker 权限的终端重启/重建该容器,再重新点击“一键清理缓存”确认不再 404。
- 11:39本轮新增的附件归集回归测试还没有在容器内真正执行原因仍是 Docker socket 权限拒绝。建议 Docker 权限恢复后优先运行 `server/tests/test_attachment_association_jobs.py` 新增两条测试,并在 5173 重新打开刚才那张坏票据验证自动修复是否生效。
- 11:55当前实际运行容器仍叫 `x-financial-local-linux`,且容器内 `poppler-data` 未安装、`mutool` 不存在;本轮文本层兜底已恢复 OCR 字段,但 PDF 图片预览仍会带转图失败 warning。12:04 已在当前运行容器补齐依赖并刷新本批票据预览;建议后续仍按最新 compose 统一到 `local-x-financial-linux`,避免旧容器继续抢占 5173。
- 12:23本轮没有拿到可用的浏览器自动化插件来生成真页截图已用前端构建、组件测试和真实 5173 OCR/preview 接口替代验证。建议用户侧刷新页面后重新上传同类 PDF若历史会话里旧附件卡片仍停留在旧状态则重新选择附件触发 OCR 状态刷新。
- 13:03`expense-application-fast-preview.test.mjs` 整文件仍有 12 个既有失败,失败点包括 unsupported business guidance 文案、旧申请 session 静态结构、typewriter/table 静态断言等;本次新增的直提申请用例已通过。建议后续单独清理这些陈旧断言,避免掩盖真正的申请链路回归。
- 13:20本轮完成了模型计划接线和结构测试但没有在真实 5173 页面输入框回放 `/steward/plans` 返回 `llm_function_call` 的端到端路径。建议后续用真实模型配置测试一次“去上海出差...直接提交”,确认模型返回任务计划后前端进入申请核对表。
- 13:35本轮已经用模型规划入口、fallback 解析和前端构建验证了“保存草稿”变体,但还没有在真实 5173 输入框回放一次完整点击/保存草稿链路。建议后续用当前句子做真页验证,确认草稿保存后出现申请单详情入口。
- 13:58本轮已经用容器后端测试和前端模型计划归一测试验证 function calling contract但还没有在真实 5173 页面观察 `/steward/plans` 的网络返回是否包含新的 `requested_action`。建议后续打开浏览器 DevTools 或后端日志,用真实模型配置回放一次保存草稿话术。
- 14:33真实接口已确认模型必经和 3 次重试,但当前主模型 MiniMax 连续超时backup 槽位 API key 未解密/未配置,因此仍然进入 `rule_fallback`。建议下一步先修复模型连通性或配置可用 backup再确认返回 `planning_source=llm_function_call` 的成功路径。
- 14:33用户提出 LangChain 方向是合理的但本轮没有直接引入新依赖。15:14 已完成 LangGraph 第一阶段迁移;后续仍要继续补 memory/checkpoint、action node 和人审中断能力。
- 15:14LangGraph 第一阶段只替换了 `/steward/plans` 的规划编排骨架,还没有把 slot decision、runtime decision、草稿保存/提交 action 和跨轮 checkpoint 迁入 graph。建议下一步按节点边界继续拆迁避免又在 LangGraph node 内写回大块自研流程。
- 15:14`langgraph` 依赖把 `websockets` 锁到 15.0.1;当前 `pip check` 和 steward 相关测试通过,但如果后续实时流或 WebSocket 功能异常,需要优先核对这个依赖变化。
- 15:22本轮只补迁移文档没有继续实现 Phase 1-5。建议下一轮严格按 `LANGGRAPH_RUNTIME_MIGRATION.md` 从模型成功路径开始推进,不要跳过文档直接扩写 graph node。
- 15:31当前 `StewardGraphRuntime` 已覆盖 slot/runtime decision但保存草稿、提交审批、关联申请单等副作用动作还只是下游既有服务执行并未接成 LangGraph 白名单 action node。建议下一步优先实现 `steward_action_nodes.py` / action contract再接 checkpoint 和 human-in-the-loop。
- 15:53当前已完成服务端 action contract但还没有实现真正执行副作用的 action registry / action node`save_application_draft``submit_application``create_reimbursement_draft` 仍需要下一步接到现有业务服务并补幂等、确认来源和 trace。建议下一步先做 action registry再接 checkpoint/human-in-the-loop。
- 16:13第一版 action executor 和 AI 工作台点击执行闭环已可执行申请/报销基础副作用,但还没有接入 LangGraph checkpoint / interrupt也没有给所有副作用写持久化幂等 trace。16:40 已完成后端 action graph runtime、pending interrupt 和幂等重放,剩余前端恢复 UI 还需单独做。
- 16:13`link_existing_application``associate_attachments` 已进入执行白名单并在未接线时阻断,但真实执行还没有统一接入 action executor。16:40 已接入真实执行和回归测试,后续仍需在真页补充可视化恢复/继续动作。
- 16:40真实 `/steward/plans` 仍未跑到 `planning_source=llm_function_call`,当前阻塞点是 backup skipped、MiniMax 三次失败;代码层 backup tool-call 成功路径已有测试,但环境层还要修模型配置或连通性。
- 16:40后端已把 pending interrupt 写进 conversation checkpoint但 AI 工作台还没有在刷新或重新进入会话时从 checkpoint 主动恢复“继续/确认”按钮;建议下一步补前端恢复 UI。
- 16:53`usePersonalWorkbenchAiMode.js` 当前已超过 800 行,后续继续加 AI 模式功能前建议拆出模型规划等待态、普通对话启动和申请预览桥接 composable避免继续扩大主运行时文件。
- 16:59本轮没有新增独立遗留风险仍沿用 16:40 的真实模型连通性问题和 16:53 的主 composable 过大问题。建议后续先修 `llm_function_call` 成功路径,再拆分 AI 模式前端运行时文件。
- 17:26`workbench-ai-mode-switch.test.mjs` 仍有 2 个旧静态断言失败,分别断言附件卡片还在主模板内联、`selectedFiles` watch 还在主 AI 模式文件里;当前真实代码已经拆成组件/composable。建议后续单独整理这个老测试文件避免它继续误报已迁移结构。
- 17:40`workbench-ai-mode-expense-scene-action.test.mjs``expense-application-fast-preview.test.mjs` 仍有多条旧静态断言失败,失败点主要是代码已拆到 composable 后测试还读取旧 Vue 单文件或固定片段。建议后续把这些静态断言改成导入 composable/model 的行为测试,避免真实功能修复被旧结构误报淹没。
- 17:48本轮修复已用模型归一化和相邻前端测试覆盖但还没有在真实 5173 页面输入最新无动作话术截图回放。建议下一步刷新 AI 工作台用“2026-02-20 至 2026-02-23去上海出差辅助国网仿生产服务器部署交通火车”验证是否直接进入申请核对表。
## TODO
- [x] ~~检查 `agent-change-log` 的 frontmatter、触发描述和日志模板是否完整。~~(完成于 09:42证据`rg` 命中 frontmatter、日志路径和三段式标题`git diff --check` 无输出)
- [x] ~~将 `AGENTS.md` 接入“每次修改后调用日志 Skill”的项目级规则。~~(完成于 09:42证据`AGENTS.md` 已新增 `变更日志 Skill 规范`
- [x] ~~如果需要把 `agent-change-log` 随仓库提交,确认是调整 `.gitignore` 放行还是用强制添加方式纳入版本管理。~~(完成于 09:44证据`.gitignore` 已只放行 `.codex/skills/agent-change-log/**`
- [x] ~~把 Git 拉取检查和其他智能体 upstream 提交分析纳入 `agent-change-log`。~~(完成于 10:01证据Skill 已新增 `Required Git Check``AGENTS.md` 已要求日志前运行 `git fetch --all --prune``git log HEAD..@{u}`
- [x] ~~把本地 ahead 提交也纳入 `agent-change-log` 的提交分析范围。~~(完成于 10:02证据Skill 和 `AGENTS.md` 已新增 `git log @{u}..HEAD`,并记录 `9321260` 本地 ahead 提交)
- [x] ~~补上提交后自动写日志的可执行脚本和 hook 模板。~~(完成于 10:21证据`update_change_log.py` dry-run 与真实写入成功,`.githooks/post-commit` 已新增)
- [ ] 在有 `.git` 写权限的环境执行 `tools/agent-change-log/install_post_commit_hook.sh`让提交后自动写日志真正启用。来源10:21 当前沙箱安装失败)
- [ ] 在后续每次 bugfix、新功能、重构或配置/文档修改后,调用 `agent-change-log` 并增量更新当天日志。来源09:41 用户要求)
- [ ] 重建/重启 `local-x-financial-linux`,确认容器内存在 `mutool`,并执行 OCR 定向 pytest。来源10:24 PDF 中文转图链路修复)
- [ ] 在 5173 真页重新上传/预览火车票 PDF确认预览 PNG 不再丢中文OCR 字段不再出现“乘车人/站点”等中文缺失或错位。来源10:24 PDF 中文转图链路修复)
- [ ] 重新上传 10:06 同款 PDF 或触发该票据重识别,确认新的 `meta.json` 写入 `preview_kind=image``preview_media_type=image/png`来源10:28 PNG 预览保留修复)
- [ ] 后端加载缓存版本修复后,重新上传同一 PDF确认不会再命中旧 OCR 缓存,`ocr_line_count` 和 PNG 预览都来自新转图流程。来源10:32 OCR cache key 修复)
- [ ] 在有浏览器自动化能力的环境回放 AI 工作台附件预览弹窗截图确认弹窗按侧边栏右侧主内容区居中。来源10:40 附件预览弹窗布局修复)
- [ ] 在真实浏览器回看系统设置 / 缓存管理页,确认 4 类缓存范围清单、维护操作条和错误反馈在当前主题下没有拥挤或错位。来源11:06 缓存管理 UI 重设计)
- [ ] Docker 权限恢复后运行票据夹 PDF 预览定向测试,并刷新已有 `preview_kind=pdf` 的旧票据 meta。来源10:41 PDF 保存阶段主动生成图片预览修复)
- [ ] Docker 权限恢复后运行 `server/tests/test_system_cache_endpoints.py`,确认 `/api/v1/settings/cache/clear` 在容器内清理 OCR 缓存并拒绝非管理员。来源10:56 系统缓存管理入口)
- [ ] 在 5173 系统设置页点击“一键清理缓存”,确认按钮 loading、toast 和清理明细符合预期。来源10:56 系统缓存管理入口)
- [ ] 重启/重建运行中的后端容器后,重新打开 `25be8906-d3c8-4236-934d-e769ee19d3a7` 这类旧 PDF 票据详情,确认预览接口能生成 `preview.png`,且前端根据 `image/png` blob 切到图片预览。来源10:57 运行时未加载新代码与前端 kind 同步修复)
- [ ] 重启/重建 `local-x-financial-linux` 后重新点击“一键清理缓存”,确认 `server/logs/app.log` 不再出现 `POST /api/v1/settings/cache/clear 404`并返回清理明细。来源11:09 缓存清理接口运行时未重载)
- [ ] 重启/重建 `local-x-financial-linux` 后确认 `SERVER_RELOAD=true` 已生效,再修改一个后端入口文件验证日志出现新的 `Starting X-Financial`来源11:20 容器 reload 配置修复)
- [ ] Docker 权限恢复后运行 `server/tests/test_attachment_association_jobs.py::test_attachment_association_keeps_receipt_folder_preview_and_fields_after_cache_clear``server/tests/test_attachment_association_jobs.py::test_attachment_meta_repairs_existing_pdf_fallback_from_source_receipt`来源11:39 对话归集附件预览/字段持久化修复)
- [ ] 在 5173 打开刚才清缓存后退化为 PDF/其他单据的报销单附件详情,确认 meta 自动修复后预览为 PNG识别信息恢复火车/高铁票和字段列表。来源11:39 历史坏 meta 自动修复)
- [x] ~~重新跑截图同批火车票 PDF 的 OCR 接口,确认不再返回“其他单据/空字段”。~~(完成于 11:55证据5173 `/api/v1/ocr/recognize` 返回 `2月20_武汉-上海.pdf``2月23_上海-武汉.pdf` 均为 `火车/高铁票`,并写回票据夹字段)
- [x] ~~重建或补齐当前运行容器的 `poppler-data` / `mupdf-tools`,确认 `mutool` 可用后再上传同类中文 PDF目标是同时恢复 PNG 预览和 OCR 字段。~~(完成于 12:04证据`apt-get install poppler-data mupdf-tools` 成功,`/usr/bin/mutool` 可用;两条 `/preview` 返回 `image/png`,并写入 `preview_kind=image`
- [x] ~~统一 AI 会话附件、票据夹和报销附件的预览类型判定,避免会话上传卡片继续把已生成 PNG 预览的 PDF 当成 PDF 展示。~~(完成于 12:23证据新增 `documentPreviewAssets.js`;前端相关测试 24/24 通过,真实 OCR 返回 `preview_kind=image``/preview``image/png`
- [x] ~~重新设计一键关联票据的弹窗 UI调整内边距、行间距、按钮尺寸与悬浮效果并按精致化要求收敛尺寸以恢复精致感。~~(完成于 12:52证据`npm run build` 构建成功,`receipt-folder-view.test.mjs` 测试通过,样式已应用)
- [ ] 单独修复 `expense-application-fast-preview.test.mjs` 里 12 个既有失败或拆分陈旧静态断言恢复整文件可作为申请链路回归套件使用。来源13:03 差旅申请直提链路验证)
- [ ] 在真实 5173 AI 工作台输入“去上海出差,辅助国网仿生产服务器部署,交通火车,直接提交”,确认模型计划路径命中 `/steward/plans`并在缺日期时停在申请核对表、日期补齐后进入提交前核查。来源13:20 AI 意图规划器接线)
- [x] ~~在真实 5173 AI 工作台输入“2026-02-20 至 2026-02-23上海出差国网仿生产服务器部署火车保存草稿。”确认模型计划或 fallback 计划生成申请核对表,并自动保存草稿后给出详情入口。~~(完成于 16:53证据1.5 秒内显示用户气泡和 pending 卡,约 38 秒后保存草稿 `AZ8QSX9QA`
- [ ] 在真实 5173 网络面板或后端日志核对 `/steward/plans` 返回 `planning_source=llm_function_call``tasks[0].requested_action=save_draft` 和完整 `ontology_fields` 后,再确认前端没有走 rule fallback。来源13:58 模型优先规划重构)
- [x] ~~确认真实 `/steward/plans` 不再毫秒级规则直出,并在模型不可用时按 10s * 3 次后降级。~~(完成于 14:33证据真实 5173 API 回放耗时 32.08sMiniMax attempt 1/2/3 均超时后才 `rule_fallback`
- [ ] 修复 MiniMax 连通性或配置可用 backup 模型,再用同一句保存草稿话术确认 `/steward/plans` 返回 `planning_source=llm_function_call`来源14:33 模型必经链路验证16:40 已补代码层 backup tool-call 测试,但真实环境仍 fallback
- [x] ~~评估 LangChain/LangGraph 是否作为 steward agent 层框架,引入前明确 memory、tools/action、planner、runtime state、observability 和依赖风险。~~(完成于 15:14证据已引入 LangGraph 第一阶段并默认接管 `/steward/plans` 规划编排45 个容器测试通过)
- [x] ~~将 `StewardSlotDecisionAgent` 和 `StewardRuntimeDecisionAgent` 继续迁入 LangGraph 节点,形成统一 steward graph runtime。~~(完成于 15:31证据新增 `StewardGraphRuntime`,容器内 graph runtime 相关回归 59/59 通过)
- [x] ~~为 `/steward/plans` 增加服务端白名单 `action_steps`,覆盖申请、报销、保存草稿和直接提交的基础动作规划。~~(完成于 15:53证据新增 `StewardActionPlanBuilder`,真实 5173 回放返回完整 action steps容器内相关回归 66/66 通过)
- [x] ~~为 LangGraph steward runtime 增加 checkpoint/persistence 设计,复用现有 conversation state支持暂停、恢复和可观测 trace。~~(完成于 16:40证据新增 `StewardGraphActionRuntime`,同一 `conversation_id + client_trace_id` 重放返回 `idempotent_replay=true`,未确认 action 写入 pending interrupt
- [ ] 修通真实 `llm_function_call` 成功路径,用真实模型配置验证 `/steward/plans` 不再进入 rule fallback。来源15:22 LangGraph 迁移文档action node、checkpoint 和 human-in-the-loop 后端部分已于 16:40 完成)
- [x] ~~实现 LangGraph 白名单 action node把保存草稿、提交审批、关联申请单、创建报销草稿和附件关联接入显式 action contract。~~(完成于 16:40证据`StewardGraphActionRuntime` 默认接管 `/steward/actions/execute`action executor 9/9 通过,关联任务回归 7/7 通过)
- [x] ~~实现 action registry 的未知 action 拒绝、确认来源校验、重复申请 precheck 阻断和基础业务执行。~~(完成于 16:13证据新增 `StewardActionExecutor`,容器内 action executor 5/5 和迁移回归 71/71 通过;前端点击执行回归 21/21 通过;真实 5173 未确认 submit 返回 `needs_confirmation`
- [x] ~~为第一版 `StewardActionExecutor` 增加 checkpoint / interrupt 恢复、持久化 trace 和幂等键。~~(完成于 16:40证据`StewardGraphActionRuntime` 持久化 `steward_action_checkpoint`,重复 trace 不重复建单)
- [x] ~~补齐 `link_existing_application` 和 `associate_attachments` 的真实 action executor 接线与回归测试。~~(完成于 16:40证据申请关联报销草稿测试和附件关联 runner 测试已通过)
- [ ] 前端 AI 工作台从 `steward_action_checkpoint.pending_interrupt` 恢复继续/确认按钮,避免刷新页面后只剩后端 checkpoint、用户看不到可继续 action。来源16:40 action graph runtime
- [ ] 拆分 `usePersonalWorkbenchAiMode.js` 的模型规划等待态和对话启动逻辑,优先把文件降回 800 行以内。来源16:53 回车无反馈修复)
- [x] ~~修复 AI 模式普通回复复用规划 pending 卡片,确保一次用户输入只生成一条助手消息。~~(完成于 16:59证据真页输入“你好”最终 `assistantCardCount=1`,前端相关测试 32/32 通过)
- [x] ~~让 AI 模式模型规划等待期间持续追加 thinking 事件,而不是静态显示“思考中”后一次性出现步骤。~~(完成于 17:26证据真页干净会话 thinking 数量从 2 条逐步增长到 6 条,相关前端测试 33/33 通过)
- [x] ~~修复 AI 工作台“保存草稿”后的上下文提交短句,让“提交这个单据”复用最近申请草稿而不是重新规划。~~(完成于 17:40证据`workbench-ai-application-context-submit.test.mjs``workbench-ai-application-gate-model.test.mjs` 通过)
- [x] ~~修复“完整出差信息但未说申请/报销”时只停在候选流程确认的问题,让唯一的“先发起出差申请”候选流直接进入申请预览计划。~~(完成于 17:48证据`workbench-ai-intent-planner-model.test.mjs` 新增回归通过,相邻 4 组前端测试通过)
- [x] ~~修复 AI 工作台“删除草稿”和“我要审核/待办审批”短句意图漏识别。~~(完成于 22:04证据`node --test web/tests/workbench-ai-command-intent-model.test.mjs web/tests/ai-document-query-model.test.mjs` 通过 19/19前端 build 通过)
- [ ] 在真实 5173 AI 工作台分别输入“删除草稿”和“我要审核”确认删除草稿只打开详情确认入口、审核短句直接返回待我审核单据卡片。来源22:04 短句意图修复)
- [x] ~~落地 AI 工作台统一意图识别 Markdown 设计文档,并实现第一版 Intent Frame + Action Policy。~~(完成于 22:31证据`2026-06-24-workbench-intent-frame-design.md` 已新增,`workbench-intent-frame-model.test.mjs` 4/4 与 `ai-document-query-model.test.mjs` 16/16 通过)
- [ ] 在真实 5173 AI 工作台分别输入“删除 3 天前的草稿”和“审核合规没有风险的申请”,确认系统先筛选候选而不是直接执行删除/审核。来源22:31 统一 Intent Frame 接线)
- [x] ~~修复“删除申请单草稿”不显示删除动作思考的问题。~~(完成于 22:36证据`workbench-intent-frame-model.test.mjs` 新增回归通过,相关前端测试 44/44 通过,前端 build 通过)
- [ ] 在真实 5173 AI 工作台输入“删除申请单草稿”,确认首条 thinking 明确显示“删除”高风险策略且结果只展示申请单草稿候选。来源22:36 删除动作 thinking 修复)
- [x] ~~为高风险删除/审核候选结果补充用户可见确认边界说明和实体快捷按钮文案。~~(完成于 22:42证据`workbench-intent-frame-model.test.mjs` 新增结果提示回归通过,相关前端测试 45/45 通过,前端 build 通过)
- [x] ~~修复“删除申请单草稿”高风险提示和候选卡片被 trusted HTML 安全渲染整块丢弃的问题。~~(完成于 22:49证据新增渲染级红灯测试后修复相关前端测试 46/46 通过,前端 build 通过)
- [ ] 在真实 5173 AI 工作台输入“删除申请单草稿”确认结果卡片上方显示高风险说明候选单据按钮显示“进入详情确认删除”。来源22:42 高风险结果提示补强)
- [ ] 单独修复或重写 `workbench-ai-mode-switch.test.mjs` 的旧静态结构断言,使它适配 `WorkbenchAiFileStrip` 和 OCR composable 拆分后的真实代码。来源17:26 额外测试发现)
- [ ] 单独修复或重写 `workbench-ai-mode-expense-scene-action.test.mjs``expense-application-fast-preview.test.mjs` 中继续扫描旧 Vue 单文件的静态断言,改为覆盖 composable/model 的行为测试。来源17:40 上下文提交验证)
- [ ] 在真实 5173 AI 工作台回放“2026-02-20 至 2026-02-23去上海出差辅助国网仿生产服务器部署交通火车”确认唯一申请候选流直接生成申请核对表。来源17:48 无动作话术直进申请预览修复)

View File

@@ -0,0 +1,109 @@
---
name: write-development-docs
description: 为 X-Financial 落地开发文档。Use when a task asks to write, create, update, or standardize planning/development documentation under document/development, especially when the expected output is a CONCEPT.md capability document plus a TODO.md implementation checklist following this repository's existing development-doc format.
---
# Write Development Docs
## 目标
使用本技能为一个功能、重构、算法、页面或业务能力创建标准开发文档。
默认输出位置:
```text
document/development/<feature-slug>/
├── CONCEPT.md
└── TODO.md
```
## 工作流
1. 先阅读相邻或同类文档,优先参考 `document/development/*/CONCEPT.md``document/development/*/TODO.md`
2. 读取与本次能力相关的代码、接口、页面、测试或历史文档,不凭空写实现细节。
3. 创建或更新 `CONCEPT.md`,先写清能力边界,再写方案和验收。
4. 创建或更新 `TODO.md`,每个任务必须能回链到 `CONCEPT.md` 的章节。
5. 如果已有实现TODO 可以勾选 `[x]`,但必须写证据;没有验证的任务保持 `[ ]`
6. 完成后检查两份文档是否互相一致,避免 TODO 承诺了 CONCEPT 没定义的能力。
## 命名规则
- 目录名使用小写 kebab-case例如 `receipt-folder``agent-trace-center`
- 能力名在正文中使用清晰中文例如“票据夹功能”“Agent 链路追踪中心”。
- 两个文件名固定为 `CONCEPT.md``TODO.md`
- 不额外创建 README、CHANGELOG、SUMMARY 等文件,除非用户明确要求。
## 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`
- 如果实现发生变化,同步更新“非目标”“风险与开放问题”“本轮实现记录”。
- 不删除历史证据;除非证据已经明显错误,才替换为新证据。
## 验收检查
交付前检查:
- `CONCEPT.md``TODO.md` 都存在。
- TODO 的 `[CONCEPT: ...]` 都能在 `CONCEPT.md` 找到对应章节或语义段落。
- 已勾选项都有证据。
- 文档没有遗留模板占位符,例如 `<功能名>``TODO``待补充`
- 文档路径位于 `document/development/<feature-slug>/`

View File

@@ -0,0 +1,4 @@
interface:
display_name: "开发文档落地"
short_description: "按项目格式生成 CONCEPT 和 TODO 文档"
default_prompt: "Use $write-development-docs to create a development CONCEPT and TODO document for a new X-Financial feature."

View File

@@ -0,0 +1,132 @@
# <功能名> 概念文档
更新时间:<YYYY-MM-DD>
## 功能一句话
用一句话说明这个能力解决什么问题、服务谁、交付什么结果。
## 背景与问题
- 当前现状:
- 用户痛点:
- 业务影响:
- 为什么现在需要做:
## 目标与非目标
### 目标
- [G1]
- [G2]
- [G3]
### 非目标
- [NG1] 本轮不做:
- [NG2] 本轮不改变:
- [NG3] 后续再评估:
## 用户与场景
- 目标用户:
- 使用入口:
- 核心场景:
1.
2.
3.
- 异常场景:
-
## 功能能力
- [C1] 输入能力:
- [C2] 处理能力:
- [C3] 输出能力:
- [C4] 状态与权限:
- [C5] 边界与降级:
## 方案设计
### 前端
- 页面/组件:
- 交互状态:
- 展示规则:
- 降级处理:
### 后端
- 接口/服务:
- 权限与校验:
- 持久化:
- 降级处理:
### 算法与规则
- 输入:
- 流程:
- 输出:
- 解释:
### 数据与契约
- 核心字段:
- 状态枚举:
- 兼容策略:
- 版本/审计:
## 算法与公式
如果当前功能不涉及公式,写明:
当前功能不涉及显式数学公式。
如果涉及公式,使用如下格式:
$$
metric = input_a + input_b
$$
变量说明:
- $metric$
- $input_a$
- $input_b$
## 测试方案
后端:
-
前端:
-
集成:
-
手工验证:
-
## 指标与验收
- [A1] 功能验收:
- [A2] 性能指标:
- [A3] 质量指标:
- [A4] 安全/权限指标:
- [A5] 可观测性:
## 风险与开放问题
- 风险:
- 已处理依赖:
- 待确认:
- 降级策略:
## 本轮实现记录
-

View File

@@ -0,0 +1,71 @@
# <功能名> 开发 TODO
更新时间:<YYYY-MM-DD>
## 使用规则
- 每个 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: 功能一句话] 确认最终实现没有偏离原始目标。
证据:

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 734 KiB

View File

@@ -0,0 +1,724 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>X-Financial Expense 启动页素材系统</title>
<style>
:root {
--bg: #edf2f0;
--ink: #0f172a;
--muted: #64748b;
--surface: #ffffff;
--line: #d8e1e8;
--primary: #006b5e;
--primary-2: #0f766e;
--primary-dark: #061416;
--mint: #e1f6f1;
--gold: #f5a524;
--gold-soft: #fff2cf;
--blue: #2563eb;
--blue-soft: #e7efff;
--paper: #f8fafc;
--shadow: 0 24px 70px rgba(15, 23, 42, 0.14);
}
* { box-sizing: border-box; }
body {
margin: 0;
color: var(--ink);
background:
linear-gradient(90deg, rgba(15, 23, 42, 0.045) 1px, transparent 1px),
linear-gradient(rgba(15, 23, 42, 0.045) 1px, transparent 1px),
var(--bg);
background-size: 28px 28px;
font-family: "IBM Plex Sans", "Microsoft YaHei UI", "Microsoft YaHei", "PingFang SC", sans-serif;
letter-spacing: 0;
}
.wrap {
max-width: 1500px;
margin: 0 auto;
padding: 34px 30px 70px;
}
.hero {
display: grid;
grid-template-columns: minmax(0, 1fr) 360px;
gap: 22px;
margin-bottom: 24px;
}
.panel {
border: 1px solid rgba(15, 23, 42, 0.08);
border-radius: 8px;
background: rgba(255,255,255,0.86);
box-shadow: 0 18px 48px rgba(15, 23, 42, 0.08);
}
.hero-main {
min-height: 260px;
padding: 30px;
display: grid;
align-content: center;
}
.kicker {
width: fit-content;
min-height: 28px;
padding: 5px 10px;
border-radius: 999px;
border: 1px solid rgba(0, 107, 94, 0.18);
color: var(--primary);
background: var(--mint);
font-size: 12px;
font-weight: 900;
}
h1 {
max-width: 760px;
margin: 14px 0 10px;
font-size: 36px;
line-height: 1.12;
letter-spacing: 0;
}
.hero p,
.section p {
max-width: 860px;
margin: 0;
color: var(--muted);
font-size: 14px;
line-height: 1.62;
font-weight: 650;
}
.hero-side {
padding: 18px;
display: grid;
gap: 10px;
align-content: center;
}
.checkline {
display: grid;
grid-template-columns: 34px minmax(0, 1fr);
gap: 10px;
align-items: center;
min-height: 58px;
padding: 10px;
border: 1px solid var(--line);
border-radius: 8px;
background: #fff;
}
.checkline strong {
display: block;
font-size: 13px;
margin-bottom: 2px;
}
.checkline span {
display: block;
color: var(--muted);
font-size: 12px;
line-height: 1.35;
font-weight: 650;
}
.icon {
width: 34px;
height: 34px;
border-radius: 8px;
display: grid;
place-items: center;
color: var(--primary);
background: var(--mint);
}
.icon.gold { color: #8a5a00; background: var(--gold-soft); }
.icon.blue { color: var(--blue); background: var(--blue-soft); }
.grid {
display: grid;
grid-template-columns: 420px minmax(0, 1fr);
gap: 24px;
align-items: start;
}
.section {
padding: 20px;
margin-bottom: 24px;
}
.section h2 {
margin: 0 0 6px;
font-size: 20px;
letter-spacing: 0;
}
.phone {
width: 316px;
height: 684px;
margin: 0 auto;
padding: 9px;
border-radius: 34px;
background: #101827;
box-shadow: var(--shadow);
}
.screen {
position: relative;
overflow: hidden;
width: 100%;
height: 100%;
border-radius: 27px;
color: #fff;
background: var(--primary-dark);
}
.status {
display: flex;
justify-content: space-between;
align-items: center;
height: 30px;
padding: 0 16px;
font-size: 11px;
font-weight: 900;
}
.sys { display: flex; gap: 5px; align-items: center; }
.sig, .wifi, .bat { display: inline-block; border: 1.7px solid currentColor; }
.sig { width: 12px; height: 10px; border-top: 0; border-left: 0; transform: skew(-8deg); }
.wifi { width: 13px; height: 8px; border-bottom: 0; border-left-color: transparent; border-right-color: transparent; border-radius: 14px 14px 0 0; }
.bat { width: 18px; height: 10px; border-radius: 2px; background: linear-gradient(90deg, currentColor 68%, transparent 68%); }
.splash {
position: relative;
height: calc(100% - 30px);
padding: 48px 16px 22px;
display: flex;
flex-direction: column;
isolation: isolate;
}
.splash::before {
content: "";
position: absolute;
inset: -34px -18px auto;
height: 320px;
z-index: -1;
background:
radial-gradient(circle at 28% 18%, rgba(245, 165, 36, 0.18), transparent 26%),
linear-gradient(135deg, rgba(255,255,255,0.12), transparent 34%),
linear-gradient(90deg, rgba(255,255,255,0.07) 1px, transparent 1px),
linear-gradient(rgba(255,255,255,0.07) 1px, transparent 1px);
background-size: auto, auto, 24px 24px, 24px 24px;
}
.topline {
display: flex;
justify-content: space-between;
color: rgba(255,255,255,0.68);
font-size: 10px;
font-weight: 900;
letter-spacing: 0.08em;
}
.lockup {
display: grid;
justify-items: center;
gap: 15px;
margin-top: 42px;
text-align: center;
}
.brand-mark {
position: relative;
display: grid;
place-items: center;
width: 74px;
height: 74px;
border-radius: 22px;
background: linear-gradient(145deg, #0b3b36, #00806f);
box-shadow: 0 24px 58px rgba(0,0,0,0.36), 0 0 0 1px rgba(255,255,255,0.16) inset;
}
.brand-mark::before,
.brand-mark::after {
content: "";
position: absolute;
width: 20px;
height: 42px;
border-radius: 999px;
background: rgba(255,255,255,0.92);
transform: rotate(28deg);
}
.brand-mark::before { left: 20px; top: 15px; }
.brand-mark::after { right: 20px; bottom: 15px; opacity: 0.72; }
.splash-name {
font-size: 25px;
line-height: 1.05;
font-weight: 950;
}
.splash-tag {
max-width: 230px;
margin-top: 8px;
color: rgba(255,255,255,0.68);
font-size: 12px;
line-height: 1.48;
font-weight: 750;
}
.scene {
position: relative;
height: 252px;
margin-top: 34px;
border-radius: 8px;
border: 1px solid rgba(255,255,255,0.13);
overflow: hidden;
background:
linear-gradient(90deg, rgba(255,255,255,0.07) 1px, transparent 1px),
linear-gradient(rgba(255,255,255,0.07) 1px, transparent 1px),
linear-gradient(145deg, #062425, #0a5f56 58%, #103a36);
background-size: 22px 22px, 22px 22px, auto;
box-shadow: 0 22px 50px rgba(0,0,0,0.34);
}
.device {
position: absolute;
left: 18px;
top: 28px;
width: 128px;
height: 188px;
border-radius: 8px;
border: 1px solid rgba(255,255,255,0.2);
background: rgba(3, 21, 24, 0.72);
box-shadow: 0 18px 36px rgba(0,0,0,0.28);
}
.device span {
display: block;
height: 8px;
margin: 8px 14px;
border-radius: 999px;
background: rgba(255,255,255,0.2);
}
.device span:first-child {
width: 72%;
margin-top: 26px;
background: rgba(255,255,255,0.78);
}
.amount {
position: absolute;
left: 14px;
right: 14px;
bottom: 18px;
padding: 11px;
border-radius: 8px;
background: rgba(255,255,255,0.08);
font-size: 19px;
font-weight: 950;
}
.amount small {
display: block;
margin-bottom: 3px;
color: rgba(255,255,255,0.52);
font-size: 9px;
font-weight: 900;
letter-spacing: 0.05em;
}
.asset-card {
position: absolute;
right: 15px;
top: 42px;
width: 156px;
padding: 13px;
border-radius: 8px;
background: rgba(255,255,255,0.94);
color: var(--ink);
box-shadow: 0 18px 42px rgba(0,0,0,0.28);
}
.asset-card.secondary {
top: 132px;
right: 34px;
width: 174px;
}
.chip {
display: inline-flex;
align-items: center;
min-height: 24px;
padding: 3px 9px;
border-radius: 999px;
color: #8a5a00;
background: var(--gold-soft);
font-size: 11px;
font-weight: 950;
}
.asset-card h3 {
margin: 8px 0 4px;
font-size: 15px;
line-height: 1.18;
}
.asset-card p {
margin: 0;
color: var(--muted);
font-size: 11px;
line-height: 1.4;
font-weight: 750;
}
.float-pill {
position: absolute;
left: 18px;
bottom: 18px;
min-height: 32px;
padding: 7px 10px;
border-radius: 999px;
color: #d7fff7;
background: rgba(0, 91, 79, 0.75);
border: 1px solid rgba(255,255,255,0.12);
font-size: 11px;
font-weight: 950;
}
.splash-footer {
margin-top: auto;
display: grid;
gap: 11px;
text-align: center;
}
.progress {
width: 118px;
height: 4px;
margin: 0 auto;
border-radius: 999px;
background: rgba(255,255,255,0.14);
overflow: hidden;
}
.progress::after {
content: "";
display: block;
width: 66%;
height: 100%;
border-radius: inherit;
background: linear-gradient(90deg, #dffcf4, var(--gold));
}
.small-copy {
color: rgba(255,255,255,0.68);
font-size: 12px;
font-weight: 750;
}
.asset-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12px;
margin-top: 16px;
}
.asset {
min-height: 138px;
padding: 14px;
border: 1px solid var(--line);
border-radius: 8px;
background: #fff;
display: grid;
align-content: space-between;
gap: 12px;
}
.asset.dark {
color: #fff;
background: var(--primary-dark);
border-color: rgba(255,255,255,0.16);
}
.asset-title {
font-size: 13px;
font-weight: 950;
}
.asset-meta {
color: var(--muted);
font-size: 11px;
line-height: 1.4;
font-weight: 700;
}
.asset.dark .asset-meta { color: rgba(255,255,255,0.62); }
.app-icons {
display: flex;
gap: 14px;
align-items: end;
margin-top: 16px;
}
.app-icon {
position: relative;
border-radius: 22%;
background:
linear-gradient(145deg, #0b3b36, #00806f);
box-shadow: 0 16px 38px rgba(0, 91, 79, 0.26);
}
.app-icon::before,
.app-icon::after {
content: "";
position: absolute;
border-radius: 999px;
background: rgba(255,255,255,0.92);
transform: rotate(28deg);
}
.app-icon::before { left: 27%; top: 20%; width: 22%; height: 56%; }
.app-icon::after { right: 27%; bottom: 20%; width: 22%; height: 56%; opacity: 0.72; }
.app-icon.lg { width: 96px; height: 96px; }
.app-icon.md { width: 72px; height: 72px; }
.app-icon.sm { width: 48px; height: 48px; }
.spec-list {
display: grid;
gap: 10px;
margin-top: 16px;
}
.spec {
display: grid;
grid-template-columns: 180px minmax(0, 1fr);
gap: 12px;
padding: 12px;
border: 1px solid var(--line);
border-radius: 8px;
background: #fff;
font-size: 13px;
}
.spec strong {
min-width: 0;
font-weight: 950;
overflow-wrap: anywhere;
}
.spec span {
min-width: 0;
color: var(--muted);
line-height: 1.45;
font-weight: 650;
overflow-wrap: anywhere;
}
.motion {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 12px;
margin-top: 16px;
}
.frame {
padding: 14px;
border: 1px solid var(--line);
border-radius: 8px;
background: #fff;
}
.frame strong {
display: block;
margin-bottom: 6px;
font-size: 13px;
}
.frame span {
color: var(--muted);
font-size: 12px;
line-height: 1.45;
font-weight: 650;
}
.mini-scene {
height: 72px;
margin-bottom: 10px;
border-radius: 8px;
background:
linear-gradient(90deg, rgba(255,255,255,0.08) 1px, transparent 1px),
linear-gradient(rgba(255,255,255,0.08) 1px, transparent 1px),
linear-gradient(145deg, #062425, #0a5f56);
background-size: 18px 18px, 18px 18px, auto;
}
.color-row {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 8px;
margin-top: 16px;
}
.swatch {
min-height: 76px;
padding: 10px;
border-radius: 8px;
border: 1px solid rgba(15,23,42,0.1);
display: grid;
align-content: end;
color: #fff;
font-size: 11px;
font-weight: 900;
}
.swatch.light { color: var(--ink); }
@media (max-width: 1180px) {
.hero,
.grid { grid-template-columns: 1fr; }
.phone { margin-left: 0; }
}
@media (max-width: 760px) {
.wrap { padding: 22px 12px 40px; }
h1 { font-size: 28px; }
.asset-grid,
.motion,
.color-row { grid-template-columns: 1fr; }
.spec { grid-template-columns: 1fr; }
.phone { width: min(316px, 100%); }
}
</style>
</head>
<body>
<main class="wrap">
<section class="hero">
<div class="panel hero-main">
<span class="kicker">Splash asset system</span>
<h1>X-Financial Expense 启动页素材系统</h1>
<p>这页专门给 Android 开机页使用,不直接复用业务页面卡片。素材分成品牌锁定、深色背景、票据场景、启动动效和 Android 落地规格,后续开发 Compose 或原生启动页时按这里取值。</p>
</div>
<aside class="panel hero-side">
<div class="checkline"><div class="icon">01</div><div><strong>启动页先看品牌</strong><span>Logo、产品名和安全工作台定位占主导不放登录表单。</span></div></div>
<div class="checkline"><div class="icon gold">02</div><div><strong>业务只做隐喻</strong><span>票据、审批、金额作为启动素材出现,不承载真实操作。</span></div></div>
<div class="checkline"><div class="icon blue">03</div><div><strong>可直接落 Android</strong><span>提供颜色、图标尺寸、动效节奏和 SplashScreen API 参数。</span></div></div>
</aside>
</section>
<section class="grid">
<div class="panel section">
<h2>启动页预览</h2>
<p>用于 9:16 Android 手机首屏,登录态检查完成后跳转登录页或首页。</p>
<div style="height:16px"></div>
<div class="phone">
<div class="screen">
<div class="status"><span>9:41</span><span class="sys"><i class="sig"></i><i class="wifi"></i><i class="bat"></i></span></div>
<div class="splash">
<div class="topline"><span>SECURE EXPENSE</span><span>ANDROID</span></div>
<div class="lockup">
<div class="brand-mark"></div>
<div>
<div class="splash-name">X-Financial<br/>Expense</div>
<div class="splash-tag">出差申请、票据采集、报销提交、移动审批</div>
</div>
</div>
<div class="scene">
<div class="device">
<span></span><span></span><span></span>
<div class="amount"><small>本月待处理</small>¥ 3,280</div>
</div>
<div class="asset-card">
<span class="chip">AI 识别</span>
<h3>票据已归类</h3>
<p>5 张票据 · 2 项待补</p>
</div>
<div class="asset-card secondary">
<h3>审批流已同步</h3>
<p>财务复核中 · 待提醒</p>
</div>
<div class="float-pill">拍照上传</div>
</div>
<div class="splash-footer">
<div class="progress"></div>
<div class="small-copy">正在加载安全工作台</div>
</div>
</div>
</div>
</div>
</div>
<div>
<section class="panel section">
<h2>品牌与 App 图标</h2>
<p>App 图标保留双斜票据形态,避免直接使用文字。启动页 Logo 使用 74dp 等比缩放,图标安全区不低于 12dp。</p>
<div class="app-icons">
<div class="app-icon lg"></div>
<div class="app-icon md"></div>
<div class="app-icon sm"></div>
</div>
<div class="asset-grid">
<div class="asset"><div class="brand-mark" style="width:58px;height:58px;border-radius:17px"></div><div><div class="asset-title">启动 Logo</div><div class="asset-meta">74dp / 58dp / 44dp 三档,深色背景使用。</div></div></div>
<div class="asset dark"><div class="app-icon md"></div><div><div class="asset-title">Launcher Icon</div><div class="asset-meta">前景图形居中,保留 Android 自适应图标裁切空间。</div></div></div>
<div class="asset"><div class="chip">AI 识别</div><div><div class="asset-title">业务状态 Chip</div><div class="asset-meta">仅作为视觉线索,不做主操作入口。</div></div></div>
</div>
</section>
<section class="panel section">
<h2>颜色与背景</h2>
<p>启动页走深色金融安全感。绿色做品牌,金色只用于识别和进度强调,蓝色用于信息状态。</p>
<div class="color-row">
<div class="swatch" style="background:#061416">#061416<br/>启动底色</div>
<div class="swatch" style="background:#006b5e">#006B5E<br/>品牌主色</div>
<div class="swatch" style="background:#0f766e">#0F766E<br/>场景渐变</div>
<div class="swatch" style="background:#f5a524;color:#251500">#F5A524<br/>识别强调</div>
<div class="swatch light" style="background:#f8fafc">#F8FAFC<br/>票据纸面</div>
</div>
</section>
<section class="panel section">
<h2>素材拆分</h2>
<p>开发落地时建议拆成 5 个可复用素材层Compose 中可分别写成 Composable。</p>
<div class="asset-grid">
<div class="asset dark"><div class="mini-scene"></div><div><div class="asset-title">背景层</div><div class="asset-meta">深色底、细网格、顶部柔光,静态即可。</div></div></div>
<div class="asset"><div class="asset-card" style="position:static;width:auto;box-shadow:none;border:1px solid var(--line)"><span class="chip">AI 识别</span><h3>票据已归类</h3><p>5 张票据 · 2 项待补</p></div><div><div class="asset-title">票据层</div><div class="asset-meta">表达拍照上传和 OCR不显示真实敏感票据信息。</div></div></div>
<div class="asset"><div class="float-pill" style="position:static;color:#006b5e;background:var(--mint);border:0;width:max-content">拍照上传</div><div><div class="asset-title">状态层</div><div class="asset-meta">启动时轻提示900ms 后淡出。</div></div></div>
</div>
</section>
<section class="panel section">
<h2>启动动效节奏</h2>
<p>控制在 900ms 左右,不阻塞用户进入登录或首页。系统检测慢时只延长进度条,不重复播放动画。</p>
<div class="motion">
<div class="frame"><div class="mini-scene"></div><strong>0ms</strong><span>深色底和网格先出现,避免白屏。</span></div>
<div class="frame"><div class="mini-scene"></div><strong>160ms</strong><span>Logo scale 0.92 到 1透明度进入。</span></div>
<div class="frame"><div class="mini-scene"></div><strong>320ms</strong><span>产品名和定位文案淡入。</span></div>
<div class="frame"><div class="mini-scene"></div><strong>520ms</strong><span>票据场景上移 8dp进度条启动。</span></div>
</div>
</section>
<section class="panel section">
<h2>Android 落地规格</h2>
<p>先走 Android 原生启动,再进入 Compose 首页。冷启动用系统 SplashScreen业务素材用于第一个 Activity 的过渡页。</p>
<div class="spec-list">
<div class="spec"><strong>windowSplashScreenBackground</strong><span>#061416。系统启动页避免白屏和业务过渡页底色一致。</span></div>
<div class="spec"><strong>windowSplashScreenAnimatedIcon</strong><span>只放 App 图标前景,不放产品文字,防止小屏裁切。</span></div>
<div class="spec"><strong>过渡页时长</strong><span>登录态检查完成即跳转;正常 600-900ms异常最长 1800ms 后进入登录页。</span></div>
<div class="spec"><strong>Compose 拆分</strong><span>SplashBackground、BrandLockup、ExpenseScene、LoadingRail 四个组件。</span></div>
<div class="spec"><strong>无障碍</strong><span>启动页只设置应用名称 contentDescription业务装饰图不参与朗读。</span></div>
</div>
</section>
</div>
</section>
</main>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 KiB

39
remove_bg.py Normal file
View File

@@ -0,0 +1,39 @@
import sys
from PIL import Image
def remove_white_bg(input_path, output_path, threshold=235):
img = Image.open(input_path).convert("RGBA")
data = img.getdata()
new_data = []
for item in data:
r, g, b, a = item
avg = (r + g + b) / 3.0
# If it's very close to white, make it transparent
if avg > threshold and min(r,g,b) > threshold - 10:
# Feathering the alpha channel
# 255 = fully transparent
# threshold = fully opaque
factor = (avg - threshold) / (255 - threshold)
alpha = int(255 * (1 - factor))
# Clamp alpha
alpha = max(0, min(255, alpha))
# We keep the pixel white to avoid dark fringes, but lower its opacity
new_data.append((255, 255, 255, alpha))
else:
new_data.append(item)
img.putdata(new_data)
# Optional: crop the image to its bounding box
bbox = img.getbbox()
if bbox:
img = img.crop(bbox)
img.save(output_path, "PNG")
if __name__ == "__main__":
remove_white_bg(sys.argv[1], sys.argv[2])

45
remove_bg_fast.ps1 Normal file
View File

@@ -0,0 +1,45 @@
$code = @"
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
public class ImageProcessor {
public static void RemoveWhiteBg(string inputFile, string outputFile, int threshold) {
Bitmap bmp = new Bitmap(inputFile);
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
int bytes = Math.Abs(bmpData.Stride) * bmp.Height;
byte[] rgbValues = new byte[bytes];
Marshal.Copy(bmpData.Scan0, rgbValues, 0, bytes);
for (int i = 0; i < rgbValues.Length; i += 4) {
byte b = rgbValues[i];
byte g = rgbValues[i + 1];
byte r = rgbValues[i + 2];
byte a = rgbValues[i + 3];
float avg = (r + g + b) / 3f;
if (avg > threshold && r > threshold - 10 && g > threshold - 10 && b > threshold - 10) {
float factor = (avg - threshold) / (255f - threshold);
int alpha = (int)(255 * (1 - factor));
if (alpha < 0) alpha = 0;
if (alpha > 255) alpha = 255;
rgbValues[i] = 255;
rgbValues[i + 1] = 255;
rgbValues[i + 2] = 255;
rgbValues[i + 3] = (byte)alpha;
}
}
Marshal.Copy(rgbValues, 0, bmpData.Scan0, bytes);
bmp.UnlockBits(bmpData);
bmp.Save(outputFile, ImageFormat.Png);
bmp.Dispose();
}
}
"@
Add-Type -TypeDefinition $code -ReferencedAssemblies System.Drawing
[ImageProcessor]::RemoveWhiteBg("d:\Code\Project\X-Financial\web\src\assets\images\raw_documents.png", "d:\Code\Project\X-Financial\web\src\assets\images\hero-financial-decor.png", 235)
Write-Host "Done"

View File

@@ -19,6 +19,8 @@ dependencies = [
"python-dotenv>=1.0.1,<2.0.0",
"email-validator>=2.2.0,<3.0.0",
"python-multipart>=0.0.20,<1.0.0",
"jieba>=0.42.1,<0.43.0",
"langgraph>=1.2.0,<2.0.0",
"openpyxl>=3.1.5,<4.0.0",
"lightrag-hku>=1.4.16,<1.5.0",
"qdrant-client>=1.18.0,<2.0.0",

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More