chore(env): docker-compose 端口与服务配置微调并更新规则表与日志

- docker-compose(.full).yml 与 start.sh 微调端口/服务配置
- AGENTS.md 同步更新协作规范
- 更新交通/通信/差旅等财务规则表,补 2026-06-24 work-log
This commit is contained in:
caoxiaozhu
2026-06-24 12:36:03 +08:00
parent 8417a9f542
commit 545b31d32f
11 changed files with 153 additions and 9 deletions

View File

@@ -43,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`)。 - **容器映射**:宿主机 `D:\Code\Project\X-Financial` ↔ 容器内 `/app``docker-compose.yml``volumes: - .:/app``working_dir: /app`)。
- **后端 venv**:容器内位于 `/tmp/x-financial-server-venv`(环境变量 `SERVER_VENV_DIR`),不要假设宿主机上有相同的 venv。 - **后端 venv**:容器内位于 `/tmp/x-financial-server-venv`(环境变量 `SERVER_VENV_DIR`),不要假设宿主机上有相同的 venv。
@@ -51,14 +51,14 @@
## 验证规范(硬性约束) ## 验证规范(硬性约束)
> 本项目代码与运行环境以容器为唯一事实来源。所有后端测试、集成测试、依赖了 Qdrant / OnlyOffice / venv 的验证,都必须在 `x-financial-main` 容器内执行,**不要在宿主机上直接跑 pytest / pip / python**。 > 本项目代码与运行环境以容器为唯一事实来源。所有后端测试、集成测试、依赖了 Qdrant / OnlyOffice / venv 的验证,都必须在 `local-x-financial-linux` 容器内执行,**不要在宿主机上直接跑 pytest / pip / python**。
- **进入容器跑命令**(最常用): - **进入容器跑命令**(最常用):
```bash ```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 -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 x-financial-main bash`(登录后默认已在 `/app` - 交互式排查:`docker exec -it -w /app local-x-financial-linux bash`(登录后默认已在 `/app`
- **容器不可用时**(未启动、健康检查失败、镜像丢失):先 `docker compose up -d main` 恢复,再继续验证;不要绕开容器在宿主机另装 venv。 - **容器不可用时**(未启动、健康检查失败、镜像丢失):先 `docker compose up -d main` 恢复,再继续验证;不要绕开容器在宿主机另装 venv。
- **单元测试设置合理超时**避免长时间卡死。涉及外部服务Qdrant / OnlyOffice / LLM的测试要么 mock要么确认 compose 网络中依赖服务在线。 - **单元测试设置合理超时**避免长时间卡死。涉及外部服务Qdrant / OnlyOffice / LLM的测试要么 mock要么确认 compose 网络中依赖服务在线。
- **每次重构后至少运行对应服务的定向测试**;涉及公共协议时补充端到端或接口测试。 - **每次重构后至少运行对应服务的定向测试**;涉及公共协议时补充端到端或接口测试。

View File

@@ -1,7 +1,7 @@
services: services:
main: main:
image: x-financial-dev:latest image: x-financial-dev:latest
container_name: x-financial-main container_name: local-x-financial-linux
restart: unless-stopped restart: unless-stopped
depends_on: depends_on:
postgres: postgres:
@@ -15,6 +15,7 @@ services:
WEB_PORT: "${WEB_PORT:-5173}" WEB_PORT: "${WEB_PORT:-5173}"
SERVER_HOST: 0.0.0.0 SERVER_HOST: 0.0.0.0
SERVER_PORT: "${SERVER_PORT:-8000}" SERVER_PORT: "${SERVER_PORT:-8000}"
SERVER_RELOAD: "${SERVER_RELOAD:-true}"
SERVER_VENV_DIR: /tmp/x-financial-server-venv SERVER_VENV_DIR: /tmp/x-financial-server-venv
X_FINANCIAL_PREFER_ENV_FILE: "false" X_FINANCIAL_PREFER_ENV_FILE: "false"
POSTGRES_HOST: postgres POSTGRES_HOST: postgres

View File

@@ -1,13 +1,14 @@
services: services:
main: main:
image: x-financial-dev:latest image: x-financial-dev:latest
container_name: x-financial-main container_name: local-x-financial-linux
restart: unless-stopped restart: unless-stopped
environment: environment:
WEB_HOST: 0.0.0.0 WEB_HOST: 0.0.0.0
WEB_PORT: "${WEB_PORT:-5173}" WEB_PORT: "${WEB_PORT:-5173}"
SERVER_HOST: 0.0.0.0 SERVER_HOST: 0.0.0.0
SERVER_PORT: "${SERVER_PORT:-8000}" SERVER_PORT: "${SERVER_PORT:-8000}"
SERVER_RELOAD: "${SERVER_RELOAD:-true}"
SERVER_VENV_DIR: /tmp/x-financial-server-venv SERVER_VENV_DIR: /tmp/x-financial-server-venv
X_FINANCIAL_PREFER_ENV_FILE: "true" X_FINANCIAL_PREFER_ENV_FILE: "true"
ONLYOFFICE_ENABLED: "${ONLYOFFICE_ENABLED:-false}" ONLYOFFICE_ENABLED: "${ONLYOFFICE_ENABLED:-false}"

View File

@@ -98,6 +98,16 @@
- 验证:`node --test web/tests/workbench-ai-composer-components.test.mjs` 通过 8/8`npm --prefix web run build` 构建通过;`git diff --check` 无输出。 - 验证:`node --test web/tests/workbench-ai-composer-components.test.mjs` 通过 8/8`npm --prefix web run build` 构建通过;`git diff --check` 无输出。
- 影响:用户点击附件打开预览时,弹窗会避开左侧 AI 工作台侧边栏,在右侧主工作区内居中展示,截图里的“偏左/不居中”观感会收敛。 - 影响:用户点击附件打开预览时,弹窗会避开左侧 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 当成附件预览展示。 - 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` - 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 预览。 - 修改:`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 预览。
@@ -106,6 +116,104 @@
- 验证:`python3 -m py_compile server/src/app/services/receipt_folder.py server/tests/test_receipt_folder_service.py` 通过;宿主机缺少 pytest 和后端依赖,容器 pytest 又因 Docker socket 权限被拒绝,暂未完成项目要求的容器定向测试。 - 验证:`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 还需要单独刷新。 - 影响:后续新上传或重新 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/图片判断。
## 遗留问题 ## 遗留问题
- 09:41当前 Skill 是新建在项目级 `.codex/skills` 目录里,本轮可以通过文件检查验证结构,但是否被未来会话自动加载还依赖 Codex 对项目 Skill 的刷新机制。建议后续新开会话或下一次任务时确认 Skill 列表是否出现 `agent-change-log` - 09:41当前 Skill 是新建在项目级 `.codex/skills` 目录里,本轮可以通过文件检查验证结构,但是否被未来会话自动加载还依赖 Codex 对项目 Skill 的刷新机制。建议后续新开会话或下一次任务时确认 Skill 列表是否出现 `agent-change-log`
@@ -114,11 +222,20 @@
- 10:02本地 ahead 提交也可能来自其他智能体,不能只看 upstream behind。建议后续日志固定同时记录 `HEAD..@{u}``@{u}..HEAD` 两个方向。 - 10:02本地 ahead 提交也可能来自其他智能体,不能只看 upstream behind。建议后续日志固定同时记录 `HEAD..@{u}``@{u}..HEAD` 两个方向。
- 10:21自动日志触发时发现 fetch 未成功失败error: cannot open '.git/FETCH_HEAD': Operation not permitted。建议后续在有 Git 写权限和网络权限的环境里重新执行拉取检查。 - 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:21当前环境不能写 `.git/hooks`,所以 post-commit hook 模板已经入库,但尚未安装到本 checkout。建议在有 `.git` 写权限的环境执行 `tools/agent-change-log/install_post_commit_hook.sh`
- 10:24本轮未能在容器内执行 pytest也未能实际确认容器是否已安装 `mupdf-tools`。建议在 Docker 权限恢复后重建/重启 `x-financial-main`,执行定向 OCR 测试,并在 5173 用真实 PDF 票据确认预览图和 OCR 字段都保留中文。 - 10:24本轮未能在容器内执行 pytest也未能实际确认容器是否已安装 `mupdf-tools`。建议在 Docker 权限恢复后重建/重启 `local-x-financial-linux`,执行定向 OCR 测试,并在 5173 用真实 PDF 票据确认预览图和 OCR 字段都保留中文。
- 10:2810:06 那条既有票据记录已经写成 PDF fallback代码修复不会自动改写旧 meta。建议重新上传一次同一 PDF或在容器权限恢复后触发票据重识别/重建预览,确认新记录变为 PNG 预览。 - 10:2810:06 那条既有票据记录已经写成 PDF fallback代码修复不会自动改写旧 meta。建议重新上传一次同一 PDF或在容器权限恢复后触发票据重识别/重建预览,确认新记录变为 PNG 预览。
- 10:32最新 10:29 上传仍然写成 PDF说明运行中的后端可能还没加载最新代码或本轮上传发生在缓存版本修复之前。建议重启后端/重建容器后再重新上传,确认 OCR cache key 已包含 `pdf-image-ocr:` - 10:32最新 10:29 上传仍然写成 PDF说明运行中的后端可能还没加载最新代码或本轮上传发生在缓存版本修复之前。建议重启后端/重建容器后再重新上传,确认 OCR cache key 已包含 `pdf-image-ocr:`
- 10:40本轮可以确认 5173 服务可达、结构测试和生产构建通过,但当前环境没有可调用的浏览器自动化插件,项目也没有现成 Playwright/Puppeteer 依赖,所以未生成真实页面截图。建议在具备浏览器自动化的环境回放一次附件预览,确认主内容区居中效果。 - 10:40本轮可以确认 5173 服务可达、结构测试和生产构建通过,但当前环境没有可调用的浏览器自动化插件,项目也没有现成 Playwright/Puppeteer 依赖,所以未生成真实页面截图。建议在具备浏览器自动化的环境回放一次附件预览,确认主内容区居中效果。
- 11:06缓存管理 UI 本轮已完成结构测试、构建和 5173 路由可达性检查,但没有真实浏览器截图证据。建议后续在本机浏览器打开系统设置 / 缓存管理,确认视觉密度和按钮位置是否符合预期。
- 10:41票据夹 PDF 保存阶段已补主动生成图片预览,但当前沙箱不能访问 Docker socket无法运行容器内 pytest也无法刷新已有 PDF 票据的旧 meta。建议 Docker 权限恢复后先跑定向测试,再对旧记录触发 `resolve_preview` 或重识别来补 `preview.png` - 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 状态刷新。
## TODO ## TODO
@@ -130,9 +247,20 @@
- [x] ~~补上提交后自动写日志的可执行脚本和 hook 模板。~~(完成于 10:21证据`update_change_log.py` dry-run 与真实写入成功,`.githooks/post-commit` 已新增) - [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 当前沙箱安装失败) - [ ] 在有 `.git` 写权限的环境执行 `tools/agent-change-log/install_post_commit_hook.sh`让提交后自动写日志真正启用。来源10:21 当前沙箱安装失败)
- [ ] 在后续每次 bugfix、新功能、重构或配置/文档修改后,调用 `agent-change-log` 并增量更新当天日志。来源09:41 用户要求) - [ ] 在后续每次 bugfix、新功能、重构或配置/文档修改后,调用 `agent-change-log` 并增量更新当天日志。来源09:41 用户要求)
- [ ] 重建/重启 `x-financial-main`,确认容器内存在 `mutool`,并执行 OCR 定向 pytest。来源10:24 PDF 中文转图链路修复) - [ ] 重建/重启 `local-x-financial-linux`,确认容器内存在 `mutool`,并执行 OCR 定向 pytest。来源10:24 PDF 中文转图链路修复)
- [ ] 在 5173 真页重新上传/预览火车票 PDF确认预览 PNG 不再丢中文OCR 字段不再出现“乘车人/站点”等中文缺失或错位。来源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 预览保留修复) - [ ] 重新上传 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 修复) - [ ] 后端加载缓存版本修复后,重新上传同一 PDF确认不会再命中旧 OCR 缓存,`ocr_line_count` 和 PNG 预览都来自新转图流程。来源10:32 OCR cache key 修复)
- [ ] 在有浏览器自动化能力的环境回放 AI 工作台附件预览弹窗截图确认弹窗按侧边栏右侧主内容区居中。来源10:40 附件预览弹窗布局修复) - [ ] 在有浏览器自动化能力的环境回放 AI 工作台附件预览弹窗截图确认弹窗按侧边栏右侧主内容区居中。来源10:40 附件预览弹窗布局修复)
- [ ] 在真实浏览器回看系统设置 / 缓存管理页,确认 4 类缓存范围清单、维护操作条和错误反馈在当前主题下没有拥挤或错位。来源11:06 缓存管理 UI 重设计)
- [ ] Docker 权限恢复后运行票据夹 PDF 预览定向测试,并刷新已有 `preview_kind=pdf` 的旧票据 meta。来源10:41 PDF 保存阶段主动生成图片预览修复) - [ ] 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`

View File

@@ -27,6 +27,16 @@ info() { printf '%b\n' "${GREEN}[INFO]${NC} $*"; }
warn() { printf '%b\n' "${YELLOW}[WARN]${NC} $*"; } warn() { printf '%b\n' "${YELLOW}[WARN]${NC} $*"; }
error() { printf '%b\n' "${RED}[ERROR]${NC} $*"; exit 1; } error() { printf '%b\n' "${RED}[ERROR]${NC} $*"; exit 1; }
is_container() {
[ -f "/.dockerenv" ] && return 0
if [ -r /proc/1/cgroup ] && grep -Eq "(docker|containerd|kubepods)" /proc/1/cgroup 2>/dev/null; then
return 0
fi
return 1
}
if [ ! -f "$ENV_FILE" ]; then if [ ! -f "$ENV_FILE" ]; then
if [ -f "$ENV_EXAMPLE_FILE" ]; then if [ -f "$ENV_EXAMPLE_FILE" ]; then
warn ".env not found. Creating it from .env.example" warn ".env not found. Creating it from .env.example"
@@ -126,6 +136,10 @@ if [ "$APP_DEBUG" = "true" ]; then
DEFAULT_SERVER_RELOAD="true" DEFAULT_SERVER_RELOAD="true"
fi fi
if is_container; then
DEFAULT_SERVER_RELOAD="false"
fi
EFFECTIVE_SERVER_RELOAD="${SERVER_RELOAD:-$DEFAULT_SERVER_RELOAD}" EFFECTIVE_SERVER_RELOAD="${SERVER_RELOAD:-$DEFAULT_SERVER_RELOAD}"
setup_ready() { setup_ready() {