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 微调服务配置
This commit is contained in:
caoxiaozhu
2026-06-24 10:42:57 +08:00
parent ee730aa31c
commit f9553a6a1a
58 changed files with 1551 additions and 1404 deletions

View File

@@ -1,572 +0,0 @@
# X-Financial 改进路线图
> 本文档基于 2026-06-18 对代码库的算法层、业务层、工程层综合评估生成。
> 每项改进都附有文件路径佐证,便于后续定位和追踪。
> 维护规则:状态变更请在对应章节同步更新;新增改进项追加到对应优先级末尾。
## 状态约定
| 标记 | 含义 |
|---|---|
| ⏳ | 待启动 |
| 🔄 | 进行中 |
| ✅ | 已完成 |
| ⏸️ | 暂缓(需说明原因) |
| ❌ | 取消(需说明原因) |
## 优先级矩阵
| 优先级 | 编号 | 标题 | 状态 |
|---|---|---|---|
| 🔴 P0 安全 | B2 | HTTP Header 权限漏洞 | ⏳ |
| 🔴 P0 业务核心 | B1 | 审批流转交/加签/撤回/会签 | ⏳ |
| 🔴 P0 共识 | B10 | 800 行硬约束破防 | ⏳ |
| 🟠 P1 算法 | A1 | 风险评分权重自适应 | ⏳ |
| 🟠 P1 算法 | A4 | LLM 票据分类 + 字段置信度 | ⏳ |
| 🟠 P1 算法 | A7 | LLM 幻觉检测 | ⏳ |
| 🟠 P1 业务 | B6 | 规则覆盖不均衡 | ⏳ |
| 🟡 P2 业务 | B3 | 申请/报销拆表 | ⏳ |
| 🟡 P2 业务 | B4 | 可配置审批矩阵 | ⏳ |
| 🟡 P2 算法 | A2 | 异常检测自适应阈值 | ⏳ |
| 🟢 P3 算法 | A3 | 多模型异常检测集成 | ⏳ |
| 🟢 P3 算法 | A5 | 票据分类持续学习 | ⏳ |
| 🟢 P3 算法 | A6 | Prompt 模板集中管理 | ⏳ |
| 🟢 P3 算法 | A8 | 规则冗余建模 | ⏳ |
| 🟢 P3 算法 | A9 | 行为画像 fairness 保护 | ⏳ |
| 🟢 P3 业务 | B5 | 预算跨期/跨科边界 | ⏳ |
| 🟢 P3 业务 | B7 | 审计日志防篡改 | ⏳ |
| 🟢 P3 业务 | B8 | 审批 SLA 监控 | ⏳ |
| 🟢 P3 业务 | B9 | 支付与凭证对接 | ⏳ |
| 🟢 P3 算法 | A10 | 算法模块 800 行拆分 | ⏳ |
---
## 一、算法层面改进
### A1. 风险评分权重自适应调优 ⏳
**优先级**:🟠 P1
**证据**
- `server/src/app/algorithem/risk_graph/engine.py:457-465`
- `server/src/app/services/risk_rule_scoring.py:16-23`
**当前实现**
```python
risk_score = 0.35*S_rule + 0.25*S_anomaly + 0.20*S_graph + 0.15*S_policy + 0.05*S_history
```
五维权重和六因子权重均为硬编码常量,无法反映规则有效性差异。
**问题**
- 已有 `RiskObservationFeedback` 表收集人工反馈,但反馈数据**未反向更新权重**
- 不同费用类型(差旅/招待/通信)的合理权重差异大,目前一刀切
**改进方向**
- 按费用类型分组的权重向量
- 定期基于反馈数据做 logistic regression / Bayesian 更新
- 权重变更需版本化、可回滚
**验收标准**
- [ ] 权重从配置/数据库读取,不再硬编码
- [ ] 反馈数据能触发权重自动调整
- [ ] 不同费用类型可配置独立权重
- [ ] 调整过程有日志和效果对比
---
### A2. 金额异常检测自适应阈值 ⏳
**优先级**:🟡 P2
**证据**`server/src/app/algorithem/risk_graph/engine.py:221-261`
**当前实现**:固定分档阈值 `1.0x→0, 1.25x→30, 1.5x→55, 2.0x→75, 3.0x→95`
**问题**
- 通信费(小额高频)和差旅(大额低频)的"1.5x"含义完全不同
- peer p75 在新部门/新费用类型时样本稀疏
- 已识别 `peer_baseline_insufficient` 不确定性,但无冷启动方案
**改进方向**
- 改为自适应分位数(基于历史数据动态计算)
-`(费用类型 × 部门层级)` 分组维护基线
- 冷启动:全局基线 + 小样本置信度折扣
**验收标准**
- [ ] 阈值按业务维度分组,不再全局统一
- [ ] 新部门/新费用类型有冷启动策略
- [ ] 基线样本不足时有降级机制
- [ ] 增加单元测试覆盖冷启动场景
---
### A3. 多模型异常检测集成策略 ⏳
**优先级**:🟢 P3
**证据**`server/src/app/algorithem/risk_graph/anomaly_models.py`
**当前实现**5 个模型独立输出(`robust_statistics / isolation_proxy / local_outlier / temporal_jump / periodic_deviation`),无集成。
**问题**
- 多模型同时报警时聚合规则未定义
- 单模型 vs 多模型共识的严重度差异未体现
- 模型间冲突无裁决机制
**改进方向**
- 引入 `AnomalyEnsembler` 集成层
- 输出 `consensus_score` + `model_disagreement_flag`
- 高风险图谱评分区分"单点异常"和"多维共识异常"
**验收标准**
- [ ] 实现集成层并接入 engine.py
- [ ] 集成结果包含共识度指标
- [ ] 单元测试覆盖各种模型组合情况
---
### A4. LLM 票据分类与字段置信度 ⏳
**优先级**:🟠 P1
**证据**
- `server/src/app/services/document_intelligence.py:143-153``_classify_with_model` 当前 `return None`
- `server/src/app/services/document_intelligence.py:20-37`(字段抽取全正则)
**问题**
- 规则层单点支撑,非标准票据格式失效
- 无字段级置信度评分,无法判断哪些抽取值需要人工复核
- LLM 分类合并策略代码已存在但未启用
**改进方向**
1. 启用 LLM 分类层(合并逻辑可直接复用)
2. 字段抽取增加置信度:`{field: {value, confidence, source}}`
3. 低置信度字段(< 0.7)自动标记"需人工核对"
**验收标准**
- [ ] LLM 分类层启用并通过对比测试
- [ ] 每个抽取字段附带置信度评分
- [ ] 低置信度字段触发人工复核标记
- [ ] 提供准确率回归测试集
---
### A5. 票据分类持续学习 ⏳
**优先级**:🟢 P3
**证据**`server/src/app/services/document_intelligence_rules.py:120``score_bias` 硬编码)
**问题**新票种ETC 电子票、滴滴行程单新模板)需开发改代码才能识别。
**改进方向**
- 分类规则做成可配置 + 可学习
- 管理员上传样本自动更新关键词权重
- 基于历史已分类票据做 TF-IDF 训练
**验收标准**
- [ ] 后台提供分类规则管理界面
- [ ] 新票种可通过样本上传识别
- [ ] 历史数据可训练关键词权重
---
### A6. Prompt 模板集中管理 ⏳
**优先级**:🟢 P3
**证据**
- `server/src/app/services/ontology_extraction.py`
- `server/src/app/services/ontology_detection.py`
- `server/src/app/services/risk_rule_generation.py`
- `server/src/app/services/user_agent_response.py`
- `server/src/app/services/user_agent_application.py`
- `server/src/app/services/user_agent_review_core.py`
- `server/src/app/services/knowledge_rag.py:214`(查询重写硬编码在方法内)
**问题**
- Prompt 散落在 12+ 文件,无版本化、无回滚、无 A/B 测试
- 相同意图的 prompt 在不同 service 中重复
**改进方向**
- 建立 `prompts/` 集中目录 + `PromptRegistry`
-`(意图, 版本)` 管理
- 支持灰度发布和效果对比
**验收标准**
- [ ] 所有 prompt 迁移到集中目录
- [ ] 支持版本化与回滚
- [ ] 提供 A/B 测试接口
---
### A7. LLM 幻觉检测与事实校验 ⏳
**优先级**:🟠 P1
**证据**:当前系统缺少 LLM 输出的显式幻觉检测。本体解析有 `confidence` 门禁,但生成的解释文本、规则建议、对话回复无校验。
**问题**
- LLM 可能编造不存在的政策条款、错误金额阈值、虚构审批人
- 风险图谱解释文本幻觉会误导审批人
- 唯一兜底是 `data_quality_gate`,仅管输入数据质量
**改进方向**
- 关键输出(金额、政策条款、规则编号)做 grounded checkLLM 输出后用规则引擎反向校验
- 对话回复中的具体数字、日期强制引用证据片段RAG 引用)
**验收标准**
- [ ] 关键数值字段有反向校验机制
- [ ] 对话回复中的事实声明可追溯到证据
- [ ] 校验失败时有明确降级策略
---
### A8. 规则冗余/相关性建模 ⏳
**优先级**:🟢 P3
**证据**`server/src/app/services/risk_rule_scoring.py`(多规则命中简单求和/max
**问题**:相关规则同时命中时分数被夸大。如 `preapproval_absent``date_outside_trip` 可能高度相关。
**改进方向**:引入规则相关矩阵,对相关规则命中分数做去冗余折扣。
**验收标准**
- [ ] 建立规则相关矩阵
- [ ] 命中聚合时考虑冗余
- [ ] 测试验证去冗余效果
---
### A9. 行为画像 fairness 保护 ⏳
**优先级**:🟢 P3
**证据**`server/src/app/algorithem/employee_behavior_profile.py:345``evaluate_weighted_profile` / `calculate_review_priority_score`
**问题**:行为画像影响审核优先级,若基于受保护属性(性别/年龄)产生系统性偏差,会构成隐性歧视。
**改进方向**
- 增加 fairness audit 接口(按人群分组统计风险分布)
- 评分特征显式排除受保护属性
- 定期输出偏差报告
**验收标准**
- [ ] 评分特征清单明确排除受保护属性
- [ ] 提供 fairness audit API
- [ ] 定期偏差报告生成
---
### A10. 算法模块 800 行拆分 ⏳
**优先级**:🟢 P3与 B10 同源,单独追踪算法模块进度)
**证据**
- `server/src/app/algorithem/employee_behavior_profile_tag_rules.py`: **812 行** 🔴
- `server/src/app/algorithem/risk_graph/engine.py`: **794 行** 🟡 临界
**改进方向**
- `employee_behavior_profile_tag_rules.py` 按标签类别拆分(差旅类 / 招待类 / 办公类)
- `engine.py` 的 5 个评分维度rule/anomaly/graph/policy/history拆为 5 个独立打分器
**验收标准**
- [ ] 所有算法文件 ≤ 800 行
- [ ] 拆分前后行为等价(单元测试通过)
- [ ] 拆分后职责边界清晰
---
## 二、业务层面改进
### B1. 审批流转交/加签/撤回/会签 ⏳
**优先级**:🔴 P0
**证据**
- `server/src/app/services/expense_claim_workflow_constants.py`(仅 11 行固定阶段)
- `server/src/app/services/expense_claim_approval_flow.py:28``approve_claim` 串行硬编码)
- 转交/加签/撤回代码中**不存在**
**问题**
- 费控系统核心能力缺失
- 现实中领导出差无法审批是常态
- 无并行审批(会签),多人审批只能串行
- 审批节点调整需改代码
**改进方向**
- 引入审批矩阵:`费用类型 × 金额区间 × 部门` → 审批节点列表
- 支持节点动作:`{approve, reject, return, transfer, countersign, withdraw, add_approver}`
- 短期优先实现"转交"和"撤回"两个最常用动作
**验收标准**
- [ ] 支持转交(审批人转给他人)
- [ ] 支持撤回(提交人在审批中撤回)
- [ ] 支持加签(临时增加审批节点)
- [ ] 支持会签(多节点并行)
- [ ] 审批矩阵可后台配置
- [ ] 关键操作有审计日志
---
### B2. HTTP Header 权限漏洞修复 ⏳
**优先级**:🔴 P0安全
**证据**`server/src/app/api/deps.py:33-213`,通过 `X-Auth-Username / X-Auth-Role-Codes / X-Auth-Is-Admin` 等请求头识别身份。
**问题**
- **任何人只要在请求头加 `X-Auth-Is-Admin: true` 就能获得管理员权限**
- 没有 token、没有签名、没有任何校验
- 足以让所有费控规则形同虚设
**改进方向**
- 引入真正的身份认证JWT 或 session cookie
- 角色信息从服务端 session/token 获取,**绝不信任客户端传来的角色声明**
- 短期方案前置网关nginx剥离这些头并注入真实身份
**验收标准**
- [ ] 客户端无法通过伪造 Header 越权
- [ ] 所有角色信息来自服务端校验
- [ ] 现有 API 行为兼容(不破坏调用方)
- [ ] 安全测试覆盖权限边界
---
### B3. 申请单与报销单拆表 ⏳
**优先级**:🟡 P2
**证据**
- `server/src/app/models/financial_record.py``ExpenseClaim` 通过 `expense_type` 后缀 + `claim_no` 前缀区分
- `server/src/app/models/reimbursement.py``ReimbursementRequest` 几乎废弃service 仅 54 行 CRUD
**问题**
- 查询复杂度高,每个查询都要带 `expense_type IN (...)` 过滤
- 字段冗余(申请单无发票字段但表里有)
- 业务语义混乱("claim"分不清是申请还是报销)
- 索引难优化
**改进方向**(需决策):
- 方案 A保守保留单表增加 `claim_kind` 字段(`application` / `reimbursement`)显式区分
- 方案 B彻底拆分为 `ExpenseApplication` + `ExpenseReimbursement` 两张表,通过 `application_id` 外键关联
- **涉及数据迁移,需用户确认方案**
**验收标准**
- [ ] 方案决策完成
- [ ] 数据迁移脚本可重入、可回滚
- [ ] 迁移前后数据等价校验
- [ ] 现有 API 行为兼容或平滑升级
---
### B4. 可配置审批矩阵 ⏳
**优先级**:🟡 P2
**证据**`server/src/app/services/expense_claim_approval_routing.py``_APPLICATION_BUDGET_REVIEW_USAGE_THRESHOLD = 90%` 等阈值硬编码)
**问题**:什么金额走什么审批、什么情况要预算管理者介入,全部硬编码。不同公司/部门差异大,无法运维配置。
**改进方向**:建立审批矩阵配置表:
```
approval_matrix(expense_type, amount_range, department_level, risk_level)
→ [approver_roles, parallel_or_serial, sla_hours]
```
管理员后台维护,系统按矩阵动态生成审批流。
**验收标准**
- [ ] 审批矩阵可后台配置
- [ ] 系统按矩阵动态生成审批流
- [ ] 配置变更有版本和审计
- [ ] 矩阵未命中时有合理默认值
---
### B5. 预算管控跨期/跨科边界 ⏳
**优先级**:🟢 P3
**证据**
- `server/src/app/services/budget.py`780 行)
- `server/src/app/services/expense_claim_budget_flow.py`112 行)
- 预算占用/释放/核销/转移已实现,但边界场景验证不足
**潜在漏洞**
- 跨财年结转:去年冻结的预算今年初未释放
- 跨期占用Q1 提交的申请 Q2 才审批,占用的是哪个季度?
- 跨科目调剂:差旅预算不够能否临时挪用办公预算?
- 无对应单元测试
**改进方向**
- 增加预算状态周期性对账任务(每日扫描 orphan reservation
- 跨期策略明确化(默认跟随申请提交期,可配置)
- 补充跨期/跨科目单元测试
**验收标准**
- [ ] 跨期/跨科目边界单元测试覆盖
- [ ] 周期性对账任务上线
- [ ] orphan reservation 自动清理
---
### B6. 规则覆盖不均衡补齐 ⏳
**优先级**:🟠 P1
**证据**`server/rules/risk-rules/` 38 条规则分布:
- 差旅travel13 条
- 预算budget13 条
- 申请application5 条
- 报销reimbursement7 条
- 标准standard5 条
**问题**
- **招待费、市场推广、培训费、福利费、软件服务费几乎没有专门规则**
- 缺少供应商关联方交易、连号发票重复报销、跨年度重复报销检测
- 这些恰是真实费控场景最易出问题的领域
**改进方向**
1. 招待费规则(参与人数缺失、人均超标、同城招待、节假日招待)
2. 供应商风险规则(同一供应商高频、关联方、工商信息异常)
3. 重复报销检测(发票号哈希去重、跨期扫描)
**验收标准**
- [ ] 招待费规则集≥5 条)
- [ ] 供应商风险规则集≥3 条)
- [ ] 重复报销检测规则≥2 条)
- [ ] 每条新规则有对应单元测试
---
### B7. 审计日志防篡改 ⏳
**优先级**:🟢 P3
**证据**
- `server/src/app/models/audit_log.py`
- `server/src/app/services/audit.py`72 行)
- before/after JSON 快照完整,但**无 hash chain 或数字签名**
**问题**:数据库管理员(或有 DB 写权限的人)可静默篡改审计日志。
**改进方向**
- 每条日志附加 `prev_hash + current_hash = sha256(prev_hash + payload)`
- 定期锚定到外部存证(区块链 / 公证处 / WORM 存储)
**验收标准**
- [ ] 审计日志实现 hash chain
- [ ] 篡改可被检测
- [ ] 外部存证机制(至少文档化)
---
### B8. 审批 SLA 与时效监控 ⏳
**优先级**:🟢 P3
**证据**:审批节点无超时提醒代码。
**问题**:单据卡在某领导处一周无人管,系统无感知。
**改进方向**
- 每个审批节点配置 SLA如 24h/48h
- 后台定时任务扫描超时单据
- 自动催办 / 升级到上级 / 转交
**验收标准**
- [ ] SLA 可配置
- [ ] 超时自动催办
- [ ] 超时升级机制
- [ ] SLA 报表可查
---
### B9. 支付与凭证对接 ⏳
**优先级**:🟢 P3业务延伸方向
**证据**:状态机到 `paid` 就结束,无银企直连、无会计凭证生成。
**问题**:报销审批通过后仍需财务人工付款、手工录凭证,未形成完整闭环。
**改进方向**
- 银企直连(用友 / 金蝶 / 远光 API
- 自动生成会计凭证(借:管理费用-差旅,贷:银行存款/应付职工薪酬)
**验收标准**
- [ ] 至少接入一个财务系统
- [ ] 凭证自动生成
- [ ] 支付状态回传
---
### B10. 800 行硬约束拆分(业务模块) ⏳
**优先级**:🔴 P0
**证据**services/ 下 ≥ 800 行的文件,共 **20 个**
| 文件 | 行数 | 超标幅度 |
|---|---|---|
| `services/user_agent_application.py` | 1451 | +81% |
| `services/risk_rule_template_executor.py` | 1164 | +45% |
| `services/expense_claim_draft_flow.py` | 1064 | +33% |
| `services/expense_claims.py` | 1042 | +30% |
| `services/receipt_folder.py` | 1034 | +29% |
| `services/steward_planner.py` | 935 | +17% |
| `api/v1/endpoints/agent_assets.py` | 925 | +16% |
| `services/orchestrator_execution.py` | 900 | +12.5% |
| `services/finance_dashboard.py` | 884 | +10.5% |
| `services/knowledge_rag.py` | 877 | +9.6% |
| `services/settings.py` | 873 | +9.1% |
| `services/agent_assets.py` | 856 | +7% |
| `services/employee.py` | 850 | +6.25% |
| `services/employee_behavior_profile_service.py` | 823 | +2.9% |
| `services/risk_rule_generation.py` | 821 | +2.6% |
| `services/agent_foundation_asset_topup.py` | 809 | +1.1% |
| `services/ontology_extraction.py` | 808 | +1% |
| `services/demo_company_simulation_seed.py` | 805 | +0.6% |
| `services/knowledge.py` | 800 | 临界 |
| 另约 20 个文件在 700-800 行区间 | | 🟡 |
**前端超大文件**
| 文件 | 行数 |
|---|---|
| `web/src/views/scripts/TravelReimbursementCreateView.js` | 4066 🔴🔴 |
| `web/src/views/scripts/TravelRequestDetailView.js` | 2861 🔴🔴 |
| `web/src/views/scripts/useTravelReimbursementSubmitComposer.js` | 2173 🔴🔴 |
| `web/src/composables/useRequests.js` | 1799 🔴 |
| `web/src/views/scripts/travelReimbursementReviewModel.js` | 1662 🔴 |
| 多个 `.vue` 文件 | 800-1130 🔴 |
**改进方向**
- 按 AGENTS.md 既定的拆分原则(编排 / 状态 / 持久化 / 权限 / 文件存储 / OCR / 规则审核 / 响应构建 / 序列化)逐个拆
- 优先 Top 5`user_agent_application` / `risk_rule_template_executor` / `expense_claim_draft_flow` / `expense_claims` / `receipt_folder`
- 每次拆分配套定向测试
**验收标准**
- [ ] 所有类/文件 ≤ 800 行
- [ ] 拆分前后行为等价(测试通过)
- [ ] 拆分后职责边界清晰
- [ ] CI 中加入行数检查(防止回潮)
---
## 三、推进原则
1. **P0 优先**B2安全、B1核心能力、B10共识必须先行。
2. **算法优化在 P0 落地后做**:再准的算法也会被权限漏洞和流程缺失抵消。
3. **小步快跑**:每项改进拆成可独立验证的子任务,配套测试。
4. **不破坏既有协议**:对外 API 尽量稳定,内部实现先拆。
5. **800 行约束**所有改动前后检查受影响类行数CI 加入行数门禁。
---
## 四、变更日志
| 日期 | 变更 | 操作人 |
|---|---|---|
| 2026-06-18 | 路线图初始版本,基于代码库全量评估生成 | Sisyphus |

View File

@@ -1,177 +0,0 @@
# Steward Application Reimbursement State Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Build a persistent ontology-bound steward state for travel application and travel reimbursement flows.
**Architecture:** Keep the existing steward planning UI and assistant delegation flow. Add a backend state layer that stores `steward_state` in `AgentConversation.state_json`, merges LLM/rule output as patches, and rejects fields outside the ontology registry before downstream services consume them.
**Tech Stack:** FastAPI, SQLAlchemy JSON state, Pydantic schemas, pytest in Docker `x-financial-main:/app`, Vue/Vite frontend.
---
### Task 1: Backend State Contract
**Files:**
- Modify: `server/src/app/schemas/steward.py`
- Create: `server/src/app/services/steward_flow_state.py`
- Test: `server/tests/test_steward_flow_state.py`
- [ ] **Step 1: Write failing tests**
```python
def test_state_merge_keeps_application_and_reimbursement_flows():
service = StewardFlowStateService()
state = service.merge_state(
{},
StewardFlowStatePatch(
active_flow="travel_application",
flow_id="travel_application",
intent="travel_application_create",
fields={"expense_type": "travel", "location": "上海"},
),
)
state = service.merge_state(
state,
StewardFlowStatePatch(
active_flow="travel_reimbursement",
flow_id="travel_reimbursement",
intent="travel_reimbursement_draft",
fields={"amount": "708", "invoice_no": "NO-1"},
),
)
assert state["flows"]["travel_application"]["fields"]["location"] == "上海"
assert state["flows"]["travel_reimbursement"]["fields"]["amount"] == "708"
```
```python
def test_state_merge_filters_non_ontology_fields():
service = StewardFlowStateService()
state = service.merge_state(
{},
StewardFlowStatePatch(
active_flow="travel_application",
flow_id="travel_application",
intent="travel_application_create",
fields={"location": "上海", "invented_field": "x"},
),
)
assert state["flows"]["travel_application"]["fields"] == {"location": "上海"}
```
- [ ] **Step 2: Run red tests**
Run:
```bash
docker exec -w /app -e SERVER_VENV_DIR=/tmp/x-financial-server-venv x-financial-main /tmp/x-financial-server-venv/bin/pytest -q server/tests/test_steward_flow_state.py
```
Expected: fail because `steward_flow_state.py` does not exist.
- [ ] **Step 3: Implement minimal state service**
Create `StewardFlowStatePatch`, `StewardFlowStateService.merge_state`, ontology field filtering, and event append logic.
- [ ] **Step 4: Run green tests**
Run the same pytest command and expect pass.
### Task 2: Steward Plan Persistence
**Files:**
- Modify: `server/src/app/schemas/steward.py`
- Modify: `server/src/app/api/v1/endpoints/steward.py`
- Modify: `server/src/app/services/agent_conversations.py`
- Test: `server/tests/test_steward_planner.py`
- [ ] **Step 1: Write failing API/service test**
Add a test proving `/steward/plans` response contains `conversation_id` and `steward_state` when `context_json.session_type = steward`, and the state contains two flows when the input contains one application and one reimbursement task.
- [ ] **Step 2: Run red test**
Run:
```bash
docker exec -w /app -e SERVER_VENV_DIR=/tmp/x-financial-server-venv x-financial-main /tmp/x-financial-server-venv/bin/pytest -q server/tests/test_steward_planner.py
```
- [ ] **Step 3: Implement conversation state persistence**
Add `conversation_id` and `steward_state` fields to response schemas, persist state through `AgentConversationService`, and merge planner tasks into `steward_state`.
- [ ] **Step 4: Run green test**
Run the same pytest command and expect pass.
### Task 3: Runtime Decision Reads Persistent State
**Files:**
- Modify: `server/src/app/services/steward_runtime_decision_agent.py`
- Test: `server/tests/test_steward_runtime_decision_agent.py`
- [ ] **Step 1: Write failing test**
Add a test proving runtime decision uses `context_json.conversation_state.steward_state` when `runtime_state` is empty.
- [ ] **Step 2: Implement minimal fallback hydration**
Normalize runtime state by merging request runtime state with persisted steward state.
- [ ] **Step 3: Run green test**
Run:
```bash
docker exec -w /app -e SERVER_VENV_DIR=/tmp/x-financial-server-venv x-financial-main /tmp/x-financial-server-venv/bin/pytest -q server/tests/test_steward_runtime_decision_agent.py
```
### Task 4: Frontend State Carry
**Files:**
- Modify: `web/src/views/scripts/stewardPlanModel.js`
- Modify: `web/src/views/scripts/TravelReimbursementCreateView.js`
- Modify: `web/src/views/scripts/useTravelReimbursementSessionState.js`
- [ ] **Step 1: Preserve steward state from backend responses**
Normalize `conversation_id` and `steward_state` from plan/runtime responses into the local session model.
- [ ] **Step 2: Send steward state in later requests**
Include the current `steward_state` under `context_json` for plan and runtime decision calls.
- [ ] **Step 3: Build frontend**
Run:
```bash
docker exec -w /app/web x-financial-main npm run build
```
Expected: build succeeds.
### Task 5: Final Verification
- [ ] **Step 1: Run backend steward tests**
```bash
docker exec -w /app -e SERVER_VENV_DIR=/tmp/x-financial-server-venv x-financial-main /tmp/x-financial-server-venv/bin/pytest -q server/tests/test_steward_flow_state.py server/tests/test_steward_planner.py server/tests/test_steward_runtime_decision_agent.py server/tests/test_steward_slot_decision_agent.py
```
- [ ] **Step 2: Run frontend build**
```bash
docker exec -w /app/web x-financial-main npm run build
```
- [ ] **Step 3: Report workspace status**
Run:
```bash
git status --short
```

View File

@@ -1,159 +0,0 @@
# Refactor Under 800 Lines Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Keep every Python class and every frontend core component/module under 800 lines, while deleting proven dead code and reducing avoidable runtime overhead.
**Architecture:** Add automated code-size guardrails first, then split oversized units by existing responsibility boundaries. Preserve public APIs wherever possible, move private helpers into focused modules, and delete code only after usage checks or tests prove it is not needed.
**Tech Stack:** Vue single-file components, Vite/Node test runner, Python/FastAPI service layer, pytest inside Docker container `x-financial-main`.
---
### Task 1: Guardrails
**Files:**
- Create: `web/tests/code-size-limits.test.mjs`
- Create: `server/tests/test_code_size_limits.py`
- [x] **Step 1: Add frontend source-unit limit test**
```bash
node --test web/tests/code-size-limits.test.mjs
```
Expected current result: FAIL, listing oversized files in `web/src/components`, `web/src/composables`, `web/src/utils`, and `web/src/views`.
- [x] **Step 2: Add backend class limit test**
```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_code_size_limits.py
```
Expected current result: FAIL, listing Python classes over 800 lines.
### Task 2: Backend Python Class Split
**Files:**
- Modify: `server/src/app/services/user_agent_application.py`
- Modify: `server/src/app/services/risk_rule_template_executor.py`
- Modify: `server/src/app/services/steward_planner.py`
- Modify: `server/src/app/services/receipt_folder.py`
- Modify: `server/src/app/services/expense_claim_draft_flow.py`
- Modify: `server/src/app/services/expense_claims.py`
- Modify: `server/src/app/services/orchestrator_execution.py`
- Modify: `server/src/app/services/finance_dashboard.py`
- Modify: `server/src/app/services/agent_assets.py`
- Create focused helper or mixin files under `server/src/app/services/`
- [ ] **Step 1: Split low-risk helper groups first**
Move private formatting, parsing, label, and serialization methods into helper mixins or helper modules. Keep original public classes and method names stable.
- [ ] **Step 2: Split domain-heavy groups**
Move larger responsibility groups into named mixins:
```text
UserAgentApplicationMixin
├── application fact resolution
├── application persistence
└── application duplicate/detail helpers
RiskRuleTemplateExecutor
├── condition evaluators
├── value resolvers
└── date/window parsing
ReceiptFolderService
├── storage/meta helpers
├── editable field resolution
└── train ticket extraction
```
- [ ] **Step 3: Verify backend class limit**
```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_code_size_limits.py
```
Expected final result: PASS.
- [ ] **Step 4: Run targeted backend regression tests**
```bash
docker exec -w /app -e SERVER_VENV_DIR=/tmp/x-financial-server-venv x-financial-main /tmp/x-financial-server-venv/bin/pytest -q server/tests/test_user_agent_service.py server/tests/test_expense_claim_service.py server/tests/test_steward_planner.py server/tests/test_risk_rule_generation.py server/tests/test_agent_asset_service.py server/tests/test_reimbursement_endpoints.py
```
Expected final result: PASS or a documented pre-existing failure with evidence.
### Task 3: Frontend Source Unit Split
**Files:**
- Modify: `web/src/components/business/PersonalWorkbenchAiMode.vue`
- Modify: `web/src/views/scripts/TravelReimbursementCreateView.js`
- Modify: `web/src/views/scripts/TravelRequestDetailView.js`
- Modify: `web/src/views/scripts/useTravelReimbursementSubmitComposer.js`
- Modify remaining files reported by `web/tests/code-size-limits.test.mjs`
- Create focused modules beside the existing owners under `web/src/views/scripts/`, `web/src/components/`, `web/src/composables/`, and `web/src/utils/`
- [ ] **Step 1: Split pure helpers before stateful runtime code**
Extract pure formatting, payload normalization, label mapping, row building, and text rendering helpers. This reduces file size without changing component state ownership.
- [ ] **Step 2: Split composables and child components**
For Vue files, move stable repeated UI blocks into child components only when props/events are clear. For script modules, move independent computed builders and action helpers into colocated modules.
- [ ] **Step 3: Remove proven redundant frontend code**
Use `rg` to confirm an export, class, helper, CSS hook, or branch is unused before deleting it. If a usage is dynamic, keep it unless a regression test proves it is dead.
- [ ] **Step 4: Verify frontend source limit**
```bash
node --test web/tests/code-size-limits.test.mjs
```
Expected final result: PASS.
- [ ] **Step 5: Run targeted frontend regression tests**
```bash
node --test web/tests/workbench-ai-mode-switch.test.mjs web/tests/expense-application-fast-preview.test.mjs web/tests/expense-profile-detail-modal.test.mjs web/tests/finance-dashboard-ranking.test.mjs
npm --prefix web run build
```
Expected final result: PASS.
### Task 4: Performance And Cleanup Pass
**Files:**
- Modify only files already touched by Tasks 2 and 3 unless a usage check proves a separate dead module can be removed.
- [ ] **Step 1: Remove repeated computation inside hot paths**
Cache local computed values inside functions, avoid repeated JSON/string/date parsing loops, and prefer early returns for blocked states.
- [ ] **Step 2: Delete redundant private helpers**
Delete helpers only when all of these checks are true:
```bash
rg "helperName" server web
node --test affected-web-test.mjs
docker exec -w /app -e SERVER_VENV_DIR=/tmp/x-financial-server-venv x-financial-main /tmp/x-financial-server-venv/bin/pytest -q affected_server_test.py
```
- [ ] **Step 3: Final verification**
```bash
node --test web/tests/code-size-limits.test.mjs
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_code_size_limits.py
npm --prefix web run build
```
Expected final result: PASS.

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

@@ -1,410 +0,0 @@
# X-Financial Duplicate Code Refactor Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development or superpowers:executing-plans for multi-task execution. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Reduce duplicated business logic, renderer helpers, protocol constants, and test fixtures without changing existing behavior.
**Architecture:** Start with low-risk pure helpers, then move toward business contract consolidation. Each round must add or preserve regression tests before production code changes, and backend validation must run inside `x-financial-main`.
**Tech Stack:** Vue 3, Vite, Node test runner, FastAPI, SQLAlchemy, pytest, Docker Compose.
---
## Scope
This document records the duplicate-code audit from 2026-06-23 and defines a staged cleanup path. The first implementation slice is intentionally small: extract shared frontend conversation rendering helpers used by `markdown.js` and `aiConversationHtmlRenderer.js`.
The following areas are in scope:
- Frontend AI markdown / HTML trusted block normalization.
- Frontend reimbursement review panel model duplication.
- Workbench AI composer / attachment strip duplication.
- Backend application gate, fact extraction, amount/date/location parsing.
- Backend platform risk context helper duplication.
- Cross-layer status, expense type, document type, and risk-level taxonomy drift.
- Test helper duplication for DB sessions, FastAPI client overrides, and OCR fakes.
The following areas are out of scope for the first implementation slice:
- Changing application submission behavior.
- Changing reimbursement association decision flow.
- Changing API response shapes.
- Editing unrelated notification top bar changes already present in the worktree.
## Findings
### P0: Frontend Trusted HTML And Conversation Text Helpers
`web/src/utils/markdown.js` and `web/src/utils/aiConversationHtmlRenderer.js` both implement:
- `ALLOWED_COLON_HEADING_TITLES`
- `BUSINESS_FIELD_LABELS`
- `TRUSTED_HTML_ALLOWED_TAGS`
- `TRUSTED_HTML_ALLOWED_ATTRS`
- `splitColonHeadingLine`
- `normalizeBusinessFieldLine`
- `hasOnlyTrustedHtmlTags`
- `sanitizeTrustedHtmlBlock`
- `extractTrustedHtmlBlocks`
- `restoreTrustedHtmlBlocks`
Plan:
- [x] Create `web/src/utils/conversationTrustedHtml.js`.
- [x] Move trusted HTML sanitizing and business-line normalization into the helper.
- [x] Keep renderer-specific output differences in each renderer.
- [x] Verify both markdown and AI conversation renderers still preserve valid trusted document cards and reject unsafe trusted HTML.
Expected benefit:
- One XSS whitelist.
- One business field normalization rule.
- Less drift between AI workbench and reimbursement assistant rendering.
### P0: Reimbursement Review Panel Model Duplication
`web/src/views/scripts/travelReimbursementCreateReviewModel.js` and `web/src/views/scripts/travelReimbursementReviewPanelModel.js` duplicate review scope, fact cards, risk item mapping, risk conversation text, and message cleanup.
Plan:
- [x] Choose `travelReimbursementReviewPanelModel.js` as the shared model.
- [x] Convert create-view imports to the shared model, or make the create-view module a thin compatibility re-export.
- [x] Add behavior tests for exported review helpers before deleting duplicate code.
Expected benefit:
- One risk item mapping.
- One review fact card model.
- Lower risk when changing reimbursement review copy or drawer behavior.
### P0: Workbench AI Composer And File Strip Template Duplication
`web/src/components/business/PersonalWorkbenchAiMode.template.html` keeps two near-identical composer forms and two near-identical selected-file strips for the welcome and inline conversation states. `web/src/components/business/workbench-ai/WorkbenchAiComposer.vue` and `web/src/components/business/workbench-ai/WorkbenchAiFileStrip.vue` already exist, but the main template still duplicates the markup.
Plan:
- [x] Reuse `WorkbenchAiComposer.vue` for both welcome and inline composer surfaces.
- [x] Reuse `WorkbenchAiFileStrip.vue` for both welcome and inline selected-file strips.
- [x] Keep the parent runtime API stable by passing a proxied runtime object into shared components.
- [x] Preserve OCR state display in the shared file-strip component.
- [x] Keep input focus behavior by exposing an explicit assistant input ref setter.
Expected benefit:
- One composer markup surface.
- One selected-file/OCR badge markup surface.
- Lower maintenance cost when upload, date picker, lock state, or send-button behavior changes.
### P1: Application Gate And Fact Extraction
Backend application flow repeats checks across `user_agent_application.py`, `steward_planner.py`, `orchestrator.py`, `ontology_detection.py`, and `ontology_extraction.py`.
Plan:
- [ ] Extract application intent / context gate helpers.
- [ ] Extract application fact resolver for date, location, reason, amount, transport, and expense type.
- [ ] Route UserAgent, StewardPlanner, and Orchestrator through the same helpers.
- [ ] Preserve existing application vs reimbursement stage boundaries.
Expected benefit:
- Fewer mismatches between button actions and text-input actions.
- Less chance of direct submit re-entering slow `/orchestrator/run` paths.
- More consistent missing-field prompts.
### P1: Backend Parsing And Risk Context Utilities
Several backend modules repeat city extraction, document field lookup, item-id dedupe, Decimal conversion, endpoint normalization, and JSON error parsing.
Plan:
- [ ] Extract platform risk context helpers for item-id and document field utilities.
- [ ] Reuse existing amount utilities before adding new regex parsing.
- [ ] Share model connectivity URL/header/error helpers between RAG runtime and connectivity checks.
- [ ] Cache sorted travel-policy city names per policy snapshot.
Expected benefit:
- Less CPU churn in repeated risk/OCR loops.
- Fewer provider connectivity behavior differences.
- Easier review of platform-risk regressions.
### P1: Cross-Layer Taxonomy And Protocol Constants
Status labels, expense types, document types, risk levels, API paths, and snake_case/camelCase mappings are repeated across backend schemas, frontend services, and tests.
Plan:
- [ ] Establish read-only contract baseline from OpenAPI export.
- [ ] Export status / approval-stage registry to frontend constants.
- [ ] Consolidate expense type, document type, and risk-level taxonomy.
- [ ] Move high-churn API path and payload builders into shared frontend test helpers.
Expected benefit:
- Fewer display inconsistencies.
- Easier API evolution.
- Less brittle source-string tests.
### P2: Test Fixture Duplication
`server/tests` repeatedly defines `build_session`, `build_session_factory`, `override_db`, `build_client`, and OCR fake functions.
Plan:
- [ ] Add backend test fixtures in `server/tests/conftest.py`.
- [ ] Move OCR fake builders into a small test helper.
- [ ] Migrate tests in batches, one behavior area at a time.
Expected benefit:
- Less boilerplate in large test files.
- Easier targeted regression coverage before service refactors.
## First Slice Execution Plan
### Task 1: Lock Shared Renderer Helper Behavior
**Files:**
- Create: `web/tests/conversation-trusted-html.test.mjs`
- Create: `web/src/utils/conversationTrustedHtml.js`
- Modify: `web/src/utils/markdown.js`
- Modify: `web/src/utils/aiConversationHtmlRenderer.js`
Steps:
- [x] Add a failing Node test that imports `conversationTrustedHtml.js`.
- [x] Assert valid trusted document-card HTML is preserved through placeholder extraction and restore.
- [x] Assert unsafe tags, event handlers, and non-document hrefs are rejected.
- [x] Assert colon headings and business field lines normalize outside fenced code blocks.
- [x] Run the new test and confirm it fails because the helper does not exist.
- [x] Implement the helper with pure functions only.
- [x] Refactor both renderers to use the helper.
- [x] Run targeted renderer tests.
- [x] Run `npm --prefix web run build`.
Validation commands:
```bash
node --test web/tests/conversation-trusted-html.test.mjs
node --test web/tests/ai-conversation-html-renderer.test.mjs web/tests/travel-reimbursement-review-drawer-switch.test.mjs
npm --prefix web run build
```
## Third Slice Execution Plan
### Task 3: Reuse Workbench AI Composer Components
**Files:**
- Create: `web/tests/workbench-ai-composer-components.test.mjs`
- Modify: `web/src/components/business/PersonalWorkbenchAiMode.vue`
- Modify: `web/src/components/business/PersonalWorkbenchAiMode.template.html`
- Modify: `web/src/components/business/workbench-ai/WorkbenchAiFileStrip.vue`
- Modify: `web/src/composables/workbenchAiMode/usePersonalWorkbenchAiMode.js`
Steps:
- [x] Add a failing Node test that expects the main template to use `WorkbenchAiComposer` and `WorkbenchAiFileStrip`.
- [x] Assert the shared file strip preserves OCR state badges.
- [x] Assert the runtime exposes an input ref setter for the shared composer.
- [x] Run the new test and confirm it fails on the duplicated template.
- [x] Pass a proxied runtime object from `PersonalWorkbenchAiMode.vue` into shared components.
- [x] Replace duplicate composer and file-strip markup in the external template.
- [x] Add OCR badge markup to `WorkbenchAiFileStrip.vue`.
- [x] Run targeted workbench AI tests.
- [x] Run `npm --prefix web run build`.
Validation commands:
```bash
node --test web/tests/workbench-ai-composer-components.test.mjs
node --test web/tests/workbench-ai-composer-components.test.mjs web/tests/workbench-ai-mode-switch.test.mjs web/tests/workbench-ai-mode-expense-scene-action.test.mjs
npm --prefix web run build
```
## Remaining Execution Plan
The remaining work is intentionally split into bounded slices. Each slice extracts shared code without changing user-facing flow, API response shape, or submission semantics.
### Task 4: Frontend Application Gate Helpers
**Files:**
- Create: `web/src/composables/workbenchAiMode/workbenchAiApplicationGateModel.js`
- Create: `web/tests/workbench-ai-application-gate-model.test.mjs`
- Modify: `web/src/composables/workbenchAiMode/usePersonalWorkbenchAiMode.js`
- Modify: `web/src/composables/workbenchAiMode/useWorkbenchAiApplicationPreviewFlow.js`
Steps:
- [x] Add a failing Node test for reimbursement creation intent, submit/save text action resolution, and orphan preview detection.
- [x] Move pure gate predicates into `workbenchAiApplicationGateModel.js`.
- [x] Replace inline copies in personal workbench and application-preview flow.
- [x] Run targeted workbench AI application tests.
Validation commands:
```bash
node --test web/tests/workbench-ai-application-gate-model.test.mjs
node --test web/tests/workbench-ai-application-gate-model.test.mjs web/tests/workbench-ai-mode-switch.test.mjs web/tests/workbench-ai-action-router.test.mjs
```
### Task 5: Backend Application Fact Resolver
**Files:**
- Create: `server/src/app/services/application_fact_resolver.py`
- Create: `server/tests/test_application_fact_resolver.py`
- Modify: `server/src/app/services/steward_planner.py`
Steps:
- [x] Add failing pytest coverage for time, location, reason, transport, and task-type inference.
- [x] Extract pure resolver helpers that preserve existing planner output.
- [x] Route `StewardPlannerService` extraction wrappers through the resolver.
- [x] Run targeted planner/fact tests inside the active app container.
Validation commands:
```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_application_fact_resolver.py server/tests/test_steward_planner.py
```
### Task 6: Backend Platform Risk Context Utilities
**Files:**
- Modify: `server/src/app/services/expense_claim_platform_context_tools.py`
- Create: `server/tests/test_expense_claim_platform_context_tools.py`
- Modify: `server/src/app/services/expense_claim_platform_route_risk.py`
- Modify: `server/src/app/services/expense_claim_platform_risk.py`
Steps:
- [x] Add failing pytest coverage for context city extraction, item-id dedupe, and text-value dedupe helpers.
- [x] Add pure helper functions in `expense_claim_platform_context_tools.py`.
- [x] Route route-risk and platform-risk consumers through the shared helpers.
- [x] Run targeted platform-risk tests inside the active app container.
Validation commands:
```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_expense_claim_platform_context_tools.py server/tests/test_expense_claim_platform_risk_stage.py
```
### Task 7: Frontend Protocol Constants And Test Helpers
**Files:**
- Create: `web/src/constants/documentProtocol.js`
- Create: `web/tests/helpers/sourceSurface.mjs`
- Create: `web/tests/document-protocol-constants.test.mjs`
- Modify: `web/src/composables/workbenchAiMode/workbenchAiApplicationPreviewModel.js`
- Migrate one high-churn source-surface test to the helper.
Steps:
- [x] Add failing Node tests for status labels, document type constants, and reusable source-surface loading.
- [x] Move duplicated status labels into `documentProtocol.js`.
- [x] Reuse constants in application-preview model and request/document-center model code.
- [x] Migrate one source-surface test helper to reduce brittle boilerplate.
Validation commands:
```bash
node --test web/tests/document-protocol-constants.test.mjs web/tests/workbench-ai-mode-switch.test.mjs
```
### Task 8: Backend Test Fixture Consolidation
**Files:**
- Create: `server/src/app/test_helpers/db.py`
- Create: `server/src/app/test_helpers/__init__.py`
- Create: `server/tests/test_db_test_helpers.py`
- Modify: `server/tests/test_expense_claim_platform_risk_stage.py`
Steps:
- [x] Identify an existing small test file with duplicated session/client/OCR helpers.
- [x] Add shared DB helper while preserving its current behavior.
- [x] Migrate one test file only.
- [x] Run the migrated test plus adjacent coverage inside the active app container.
Validation commands:
```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_db_test_helpers.py server/tests/test_expense_claim_platform_risk_stage.py
```
### Task 9: Steward Planner Module Split
**Files:**
- Create: `server/src/app/services/steward_planner_shared.py`
- Create: `server/src/app/services/steward_planner_fallback.py`
- Create: `server/src/app/services/steward_planner_extraction.py`
- Modify: `server/src/app/services/steward_planner.py`
Steps:
- [x] Move shared constants and `PlannedTaskDraft` into a shared planner module.
- [x] Move off-topic, pending-flow, and rule-fallback planning into `steward_planner_fallback.py`.
- [x] Move task extraction, ontology normalization, attachment grouping, and summary helpers into `steward_planner_extraction.py`.
- [x] Keep `steward_planner.py` as the service orchestration entrypoint.
- [x] Run planner/fact resolver tests inside the active app container.
Validation commands:
```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_application_fact_resolver.py server/tests/test_steward_planner.py
```
### Task 10: App Shell Dynamic Business Chunk Loading
**Files:**
- Create: `web/src/views/scripts/appShellAsyncViews.js`
- Create: `web/src/components/shared/AppViewLoadingState.vue`
- Create: `web/src/components/shared/AppModalLoadingState.vue`
- Modify: `web/src/views/AppShellRouteView.vue`
- Modify: `web/src/components/layout/SidebarRail.vue`
- Modify: `web/src/components/layout/AiSidebarRail.vue`
- Modify: `web/tests/app-shell-route-loading.test.mjs`
- Modify: `web/tests/ai-sidebar-rail-mode.test.mjs`
Steps:
- [x] Keep top-level shell routes eager so login/setup/app layout does not blank during route navigation.
- [x] Move heavy business views behind `defineAsyncComponent` loaders.
- [x] Add an in-workarea loading state for slow business chunks.
- [x] Add a modal loading state for the smart reimbursement assistant chunk.
- [x] Preload likely next views on sidebar hover/focus and during browser idle time.
- [x] Preserve existing Vite manual vendor chunks, including `vendor-echarts`.
- [x] Run targeted frontend tests and production build.
Validation commands:
```bash
node --test web/tests/app-shell-route-loading.test.mjs web/tests/ai-sidebar-rail-mode.test.mjs web/tests/sidebar-document-unread-dot.test.mjs web/tests/workbench-ai-mode-switch.test.mjs web/tests/workbench-ai-reimbursement-association-gate.test.mjs web/tests/travel-reimbursement-review-drawer-switch.test.mjs web/tests/documents-center-status-filter.test.mjs
npm --prefix web run build
```
Result:
- App shell entry chunk after split: `index-vWyUfHfm.js` 223.75 kB, gzip 74.11 kB.
- Large `vendor-echarts` chunk remains isolated at 598.67 kB, gzip 204.84 kB; it is no longer part of the app-shell entry chunk.
- Build still reports the existing Rollup `#__PURE__` annotation warnings from Element Plus / VueUse.
## Guardrails
- Do not touch unrelated dirty files.
- Do not change renderer output intentionally in this slice.
- Do not move backend logic until frontend helper extraction is green.
- Backend tests must run through 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 <path>
```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB