# waiguan-admin **Repository Path**: zkak/waiguan-admin ## Basic Information - **Project Name**: waiguan-admin - **Description**: 智慧外管 FastapiAdmin 前端/BFF - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-15 - **Last Updated**: 2026-10-22 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 智慧外管 · FastapiAdmin 前端 用 **FastapiAdmin + 通用智能体底座(M01–M09)** 从零重做的智慧外管前端。 不是迁移旧的 Vue3+Spring BFF,而是独立 Python 服务,直接对接底座 M01 网关。 > **改代码前先读 [`MAP.md`](MAP.md)** — 分层加载导航入口(L0 边界 / L1 模块卡 / L2 graphify 派生 / L3 场景手册)。 > 干活只加载 `MAP.md` 的相关层 + 对应 `docs/scenarios/` 场景手册,取代码块用 `graphify query`,不整仓吞。 > 文档与代码同步由 `tests/test_docs_sync.py` 强制(悬空引用/模块登记/场景指针/承诺符号四类漂移)。 > 本 README 是"项目是什么/已实现什么"的说明与设计文档;MAP 是"改动时去哪找"的导航,两者互补。 > **违法事实辅助分析历史记录(2026-09-24,现行步骤和确认方式以 CLAUDE.md 及档案场景手册为准)**:从全部违法事实里**逐条提取每一个独立违法行为,每条一张卡片**,逐项列出事实摘要(当事人在何时办理什么业务时存在什么违法行为,违反了什么规定,共涉及多少金额或获利多少;不写发现经过和详细过程,不编造材料中没有的内容)、一句话定性(`qualitative_summary`)、违反的法律法规(引用原文+《法规名》+条款),由用户逐条确认。拆分+摘要+定性由 M03 `_think_facts`(deer-flow)生成、人工可改,模型不可用退化为一条并本地关键词兜底;引用法规逐条走 M01 `case-analyses`(fail-closed,不编造)。主要事实含违法主体、业务领域和违法行为。业务领域按四组多选:经常项目外汇管理、资本项目外汇管理、金融机构外汇业务监管、国际收支与外汇统计;选中的子类名称拼进检索查询,知识库条款没有这四组字段,知识图谱也不按这四组建节点。案件金额改在确认处罚意见、情节事实之前二选一填写,写入 `illegal_amount` / `illegal_income`,只供处罚测算,不参与情节判断。裁量步每卡按《外汇管理行政罚款裁量办法》第二章列出全部项(第八条 5 项、第九条 6 项、第十条 7 项、第十二条 10 项、第十三条 7 项),每项可改确认状态、事实依据和判定理由。确认证据与案情、确认法律依据、确认处罚意见三步,每类事实底部有「确认」按钮(`POST .../analysis/fact-confirm`),全部确认后顶部「确认并进入下一步」才可点;未全部确认时拒绝。BPMN 五步与进入硬门不变,多条在步骤内循环。详情以 [`docs/scenarios/archive-fact-analysis.md`](docs/scenarios/archive-fact-analysis.md) 为准。 ## 架构 ``` 浏览器 → FastapiAdmin(本服务, :8500) → 底座 M01 网关(/api/v1/*, :8001) → M02–M09 ``` - **数据源**:只对接 M01 网关 `/api/v1/*`。M02–M09 是内部服务,前端不直连。 - **认证(对底座)**:已认证本地用户通过服务端独立绑定选择 `Authorization: Bearer `;实时 M01 identity 与本地权限求交。 每请求带 `X-Request-Id`(`req_`)/`X-Trace-Id`(`trc_`);写操作带 `Idempotency-Key`; 更新用 `If-Match` 乐观锁。内部签名头由网关注入,前端不发。 - **本地存储**:管理员与机构目录在 admin 连接,档案、政策、人审和作业引用在 business 连接;文件固定版本归 M04,知识投影归 M05,不跨库建外键。 ## 当前源码接入与整改边界(2026-10-20) 本节描述当前工作树的源码与合同,不代表运行服务已加载、canonical 已合入或正式发布通过。权限集成保留已批准的 437a9db / 0203688 基线;历史审计及整改验收由主审单独归档。 - **当前用户与权限**:app/services/foundation_identity.py 从已认证 Admin 选择服务端独立用户绑定,读取 M01 当前 identity,核对 actor/tenant/app/project、有效成员与有效期;部署业务库绑定 M01_TENANT_ID。app/services/authz.py 将本地候选业务权限与对应的当前 application_scopes 求交,本地超管只免本地角色限制。读取失败或绑定错配拒绝;浏览器身份头不选取凭据。后台自动化主体须明确登记,不能成为用户缺绑定的回退。 - **研究与附件**:共享 UI 登录桥返回 WAIGUAN.M03_BRIDGE_DISABLED,不签共享会话、不联系 Provider 登录。聊天、线程、附件及产物经 BFF→M01 research→M03;multipart 上传与续答保持当前用户、线程归属。M03 私有 Provider 才持有模型配置;BFF 不构造可信 scope 或执行授权。 - **M02 结果交付**:任务 `result_delivery=restricted` 仍可观察真实执行状态,BFF/监控页面隐藏正文与引用且不缓存任务正文。识别/生成受限时不回写普通失败、不补投;SSE 前后及正文落库前复核原任务。见 [底座消费合同 v5](docs/contracts/foundation-base.md)。 - **读写分离**:GET、列表及只需读取权限的轮询以 allow_submit=False 逐级观察已有真实作业 ID,可条件回写结果投影;缺 ID 保持原事实,不启动转换、识别、知识导入或取消补偿。显式写授权入口和原授权后台恢复才可提交,不把服务参数作为浏览器授权字段。任务先按当前用户、机构、业务权限及时间范围过滤本地引用,再读取下游。 - **文档服务**:ONLYOFFICE v2 票据绑定原 owner、tenant、scope、对象和 mode,旧无归属票据拒绝;取件、回调下载后及本地提交前按原用户重新核对当前身份、读写权限和档案范围。view 不签写能力;edit 额外要求 archive:write。快照与版本 CAS 拒绝并发改文、归属变更、封存及关闭。票据可重复使用至过期,每次仍重新核权;没有跨服务撤权事务栅栏。 - **材料与事务**:材料版本变化须使分析及人工确认失效;转换投影按原资源、版本及作业快照条件回写。审批状态、版本与轨迹在同一业务库事务中执行版本/状态 CAS;其他 writer 也须避免整行写回旧状态。资源回收保护仍存在的业务引用,M04 与本地库不共享事务。相关实现及未覆盖交错由独立审计核实,不用文档宣称全部关闭。 - **冻结导入**:录入确认与政策创建/关联同事务提交,转换在提交后启动。知识化先原子抢占尝试,在首次 POST 前持久化完整 payload 与稳定幂等键;结果未知时只重放同一请求,不按后来元数据重建。JSON/版本 CAS 合并自身字段,保留条款作业及人工历史。取消保留未知受理与补偿事实;403 是授权失败,不能算来源不存在。应用事务不证明远端索引最终一致。 权威细节见 [权限求交](docs/contracts/application-permission-alignment.md)、[用户身份](docs/contracts/foundation-user-identity.md)、[ONLYOFFICE](docs/contracts/onlyoffice-authorization.md)、[档案材料](docs/scenarios/archive-fact-analysis.md)、[审批事务](docs/scenarios/archive-approval-engine.md)、[冻结导入](docs/scenarios/policy-knowledge-collection.md) 与 [文稿转换](docs/scenarios/policy-manuscript-markdown.md)。 ## 界面设计(对齐原系统 zhihui-waiguan-yudao) 视觉不自创,照原 yudao 后台 `src/styles/var.css`(注释「智慧外管政务配色」)的权威 token: | token | 值 | 说明 | |---|---|---| | 侧栏底色 | `#eaf4fb` | 浅蓝(非深色);文字 `#3e617d`,选中 `#0b2d4d`/底 `#d5e8f6` | | 顶栏 | `#0b2d4d` | 深蓝 + 白字 | | 侧栏宽 | `200px` / 折叠 `64px` | `--left-menu-max/min-width` | | logo 高 | `50px` | `--logo-height` | | 多标签栏 | `35px` | `--tags-view-height`,顶栏下方 TagsView | | 内容边距 | `20px` | `--app-content-padding` | | 主色 | `#409eff` | `--el-color-primary` | 布局(`templates/layout.html`):固定侧栏(不浮动、无投影、无悬停浮层) + 深蓝顶栏 + TagsView 多标签页(localStorage 记访问过的页面,可切换/关闭) + 内容区独立滚动。样式集中在 `app/static/waiguan.css`,组件助手 `app/static/wg-ui.js`(badge/confidence/apiError/descList/empty 等,杜绝原始 JSON 直出)。 ## FastapiAdmin 的用法(关键) FastapiAdmin 的 `Model(Resource)` 绑死 TortoiseORM 直连数据库做自动 CRUD——底座只给 REST,用不上。 所以这里用它的**外壳**(登录 / Tabler 布局 / 菜单 / 模板),页面数据全部由自写路由经 httpx 调 M01: - 菜单注册:`app/resources.py`(全部是一级 `Link`;组与组之间用无地址的 `NavDivider` 画横线) - 页面 + 数据接口:`app/routers/waiguan.py` - 底座客户端:`app/services/m01_client.py`(封装调用契约)、`app/services/gateway.py`(能力封装) - 兼容 shim:`app/compat.py`(让 fastapi-admin 旧式 `TemplateResponse` 签名适配新版 Starlette,不降级、不散改第三方源码) ## 目录 ``` app/ main.py 应用引导(挂载 FastapiAdmin + Tortoise + Redis 会话) config.py 配置(M01 地址/token、管理员账号、端口) compat.py TemplateResponse 兼容 shim(必须最先 install) models.py Admin 模型(仅登录用) providers.py 用户名密码登录 provider resources.py 左侧菜单注册 routers/waiguan.py 业务页面 + M01 代理接口 services/ foundation_identity.py / authz.py / m01_client.py / gateway.py templates/waiguan/ 各页面模板(继承 layout.html) ``` ## 运行 依赖已装在 `.venv`(Python 3.12,与底座统一)。需要本机 Redis(:6379) 和 M01 网关(:8001)。 ```bash cd /home/user/projects/waiguan-admin .venv/bin/python -m uvicorn app.main:app --host 127.0.0.1 --port 8500 # 浏览器打开 http://127.0.0.1:8500/admin;账号与初始密码由部署显式配置 ``` 环境变量覆盖:`M01_BASE_URL`、`M01_TOKEN`、`WAIGUAN_ADMIN_PORT`、`REDIS_URL`、`WAIGUAN_ADMIN_DB`。 也可用一键脚本:`./run.sh`(会前置检查 Redis/M01 是否在跑)。 ## 运维脚本(统一入口 `ops.sh`) 2026-10-20 远程测试 admin 身份恢复装配使用独立 M01 测试入口 `127.0.0.1:18001`;当前配置、验证范围和回滚见[身份故障恢复记录](docs/incidents/2026-10-20-admin-foundation-identity.md)。这不代表统一身份平台或其余底座模块已经升级验收。 把「服务检测 / 健康检测 / 隧道检测 + 隧道守护」整合到一个脚本,避免脚本碎片化: ```bash ./ops.sh check # 一次性输出全部状态:本地服务端口 + 健康端点 + 隧道 + 公网可达(一张表看全) ./ops.sh guard # 隧道守护常驻:cloudflared 挂了自动拉起,URL 变化写文件并打印(nohup ./ops.sh guard & 后台常驻) ./ops.sh url # 打印当前隧道公网 URL ./ops.sh tunnel # 前台起一次隧道(不守护) ``` - **check** 分三段:① 本地服务(Redis :6379 / M01 :8001 / 前端 :8500 端口监听)② 健康端点 (M01 `/healthz`、`/readyz`、前端 `/admin/login` 的 HTTP 码;`/readyz` 503=未装配是联调常态,标黄不标红) ③ 隧道(cloudflared 进程 + URL + 公网可达)。任一异常给出下一步提示,不只报红。 - **guard** 只管隧道(职责单一,不代客起前端),进程不在就拉起,捕获到新 URL 写入 `.tunnel-url` 并打印; 免费 trycloudflare 隧道重连换 URL 时能第一时间拿到新地址。 - 所有参数走环境变量覆盖:`WAIGUAN_ADMIN_PORT`、`M01_BASE_URL`、`WAIGUAN_REDIS_PORT`、 `WAIGUAN_TUNNEL_LOG`、`WAIGUAN_TUNNEL_URL_FILE`、`WAIGUAN_GUARD_INTERVAL`。 ### 汇智助手对话页无头实测(`ops/verify_chat_ui.py`) 对话页(`/admin/waiguan/deerflow`)的观感与滚动行为靠真浏览器验收,不进 CI: ```bash /home/user/tools/webwright-venv/bin/python ops/verify_chat_ui.py # 真登录 :8500,只桩 M03 流 ``` - playwright 刻意装在 `/home/user/tools/webwright-venv`,不装进项目 `.venv`,以免带进 CI。 - 只桩掉 `/api/deerflow/stream`(短轮+长轮两段 SSE),其余真实走;量真实像素/几何:助手回答通栏、用户消息右对齐、研究过程折叠、Enter 发送、长对话整页滚动+流式跟随到底、上翻不被拽回。 - 截图落 `/tmp/wg-chat-full.png`、`/tmp/wg-chat-area.png`。 M08 治理整改的应用消费合同见 [底座对接](docs/contracts/foundation-base.md),消费者回归、只读运行证据和安全恢复交接见 [M08 消费对齐报告](docs/audits/2026-10-20-m08-consumer-alignment.md)。 ## 政策索引二级标题增强(2026-10-21) 新建索引载体的二级标题为「条号+已确认内容摘要(nav_summary)+【主体:…;领域:…】」。复用已确认标签,空组省略、标签单行归一去重并转义 Markdown,不增加模型调用。摘要在预览前传入并纳入切片校验号;确认标签在预览后随正式导入携带,不进入 slice_set_id,但会改变 SDK 内容哈希。原文、固定 URI 和 chunk ID 保持;标题不是引用原文。 旧载体不原地改写,同键重放不重投。旧引用跨载体仅限 SDK 同固定 URI/集合、双端 sidecar 核验唯一非空条号、clause 角色和完整原文相同,且前后复核当前映射、来源版本与授权;删除条款、撤权、缺证据或变版本均拒绝,不承诺空条号或历史多片条款可跨载体重开。本轮仅配套文档与验证,未部署、未重建线上索引;后续须另获发布和显式新入库授权。详见[场景手册](docs/scenarios/clause-subject-domain-annotation.md)与[交付记录](docs/implementation/policy-heading-enhancement-20261021.md)。 ## 本地验证与类型检测 在项目虚拟环境中安装开发依赖后执行验证: ```bash python -m pip install -r requirements-dev.txt PYTHONPATH=/path/to/foundation/src:. python -m pytest -q python -m ruff check app python -m mypy --config-file mypy.ini python -m tests.test_docs_sync ``` requirements-dev.txt 引入运行依赖并固定 pytest 9.1.1、Ruff 0.16.5;mypy 由 requirements.txt 声明。类型检查使用 mypy.ini 的严格配置。测试由 tests/conftest.py 在导入前注入合成配置,禁用 dotenv 并使用隔离数据库,不使用正式凭据。安装命令用于复现验证环境,不能据此认定干净安装、全量检查或部署已经通过;最终实际结果由主审在新整改报告记录。 约定: - 底座 JSON 用共享别名 `JSONDict` / `M01Result`(在 `services/m01_client.py`),不裸用 `dict`/`Any`。 - API 路由返回 `ApiResult`(`dict | JSONResponse`),装饰器带 `response_model=None`(否则 FastAPI 会把 union 当响应模型报错)。 - `fastapi_admin` 无类型标注,`mypy.ini` 里对其 `ignore_errors` 并关掉 `disallow_subclassing_any`。 ## 已实现页面(第一期:能直连 M01 原语的部分) | 页面 | 路由 | 对接的 M01 端点 | |---|---|---| | 工作台 | `/admin/waiguan/dashboard` | `GET /healthz` `/readyz`;页内先「行政执法检查流程」八步图,再「待办事项」,最后关键指标。待办含各档案当前办案环节深链 | | 政策索引 | `/admin/waiguan/knowledge` | 三栏政策目录/Markdown正文/章节导航;只读 M01 已索引载体,支持标题筛选、URL恢复和移动端。见 [读取合同](docs/contracts/policy-index-reader.md) 与 [实施记录](docs/implementation/policy-index-reader-20261021.md)。共享检索API继续供案情等使用 | | 词条接口(无页面,2026-10-19 起) | `/admin/waiguan/policy/api/wiki*` | `POST /api/v1/wiki-knowledge/imports`、`POST /api/v1/wiki-knowledge/search`;下架/删除走 `POST /api/v1/knowledge/sources/{id}/status`。政策管理页「词条库」按钮和 `/policy/wiki` 页面已删除 | | 参考案例 | `/admin/waiguan/cases` | 本地库(`GET /waiguan/archive/api/cases`):列纳入案例库的档案;纳入走 `POST /api/v1/case-knowledge/imports`,详见页面表 | | 文书模板 | `/admin/waiguan/document-templates` | 本地表 `wg_document_template`:五类智能生成文书的全局 Word 模板,页面内 ONLYOFFICE 在线编辑后保存(浏览器走门户 :19980,不直连内网 :9980),占位符按已确认内容填充。生成的 docx 建议稿在文档预览页同样用文档服务在线编辑(保存回写 docx,不覆盖正式稿)。默认版式对齐已入档正式检查报告、立案审批表和决定书,不抄某一份档案正文;建议书和告知书没有正式件,默认只有标题 | | 资源文件 | `/admin/waiguan/resources` | `POST /api/v1/resources` | | 条款适用主体/领域标注 | 政策详情页 `clauseCard` | 从已确认固定文稿建立快照,经 M01 research→M03 一次返回锚点、一句摘要及主体/领域建议;宿主按锚点定位原文,长文按规则边界分批。人工确认后才能入库,已确认内容不被重跑覆盖;M05 clause-annotations 保持空草稿合同,主路径不逐条调用。详见 `docs/scenarios/clause-subject-domain-annotation.md` | ## 外部共享服务整合(汇智助手 / 技能市场 · 薄消费者、零改动本体) DeerFlow 是 M03 私有 Provider,skillhub 是 M06 管理界面;共享服务职责不迁入外管。外管通过 M01 消费研究能力,使用当前用户独立身份;共享工作台登录桥已停用。汇智助手保留「汇智助手对话」和「进行中对话」两块界面;技能市场保留 skill:read 说明页,并提供独立 skill:manage 原生注册、申请审批、批准/拒绝、发布与状态核实页面,统一当前用户 BFF→M01→M06;历史 skillhub 深链页兼容保留。旧直连方案仅供历史排查,见 `docs/scenarios/integrate-external-service.md`。 **内外地址必须分离**(浏览器跳转 ≠ 服务器直连): - **内部数据链**(`*_BASE_URL`,如 `http://127.0.0.1:2026`):服务器侧 `deerflow_client` 直连拉数据用。 - **外部浏览器入口**(`*_PUBLIC_URL`,公网可达):模板「打开控制台」按钮用。**绝不把 127.0.0.1 内网地址渲进 href**——否则远程用户经隧道打开外管后会连到自己的 localhost。未配置 `*_PUBLIC_URL` 时按钮禁用+提示,不回退内网地址。 - deer-flow 公网入口:`bash ~/m04-stack/launch_deerflow_tunnel.sh` 起 cloudflared 到 :2026,把公网 URL 写 `deerflow-public-url.txt`,外管 config 经 `_read_env_or_file` 读取(env 优先);重启外管生效。生产用真实域名时改统一前置 nginx **子域**路由(deer-flow 无 basePath,不能子路径挂载)。 | 页面 | 路由 | 整合方式 | 对接端点 / 边界 | |---|---|---|---| | 汇智助手 | `/admin/waiguan/deerflow` | **两块对话界面**(汇智助手对话 + 进行中对话) | `汇智助手对话` 提供对话模式(默认「思考」轻量推理)、知识库引用、附件、流式对话、新建对话和保存对话;`进行中对话` 只列当前登录用户,通过 `GET /admin/waiguan/api/deerflow/runs` 展示标题、保存状态和更新时间,并提供继续对话、重命名、删除,以及「删除其他对话」(一次删除除当前打开对话外的其余对话,复用单条 `DELETE /admin/waiguan/api/deerflow/thread/{id}`,二次确认,部分失败会提示)。保存:`POST /admin/waiguan/api/deerflow/thread/{id}/save`;退出清理:`POST /admin/waiguan/api/deerflow/discard`(只删当前用户未保存的对话)。重命名:`POST /admin/waiguan/api/deerflow/thread/{id}/rename`(Form `title`);删除:`DELETE /admin/waiguan/api/deerflow/thread/{id}`;均复用 `deerflow:use`,客户端二次确认删除、服务端 404 幂等,409 归一为 `WAIGUAN.M03_CONFLICT`。 | | 技能注册与发布 | `/admin/waiguan/skills`(旧 `/admin/waiguan/skillmarket` 保留 skill:read 说明页) | 原生页面 + 当前用户 M01 BFF | 页面 `skill:manage`;审批决定/发布额外 `skill:publish`。规范 hash 仅取审批响应;查询精确 published 后开放政策 Wiki 生成。测试与实际部署边界见 `docs/scenarios/skill-market-integration.md`。 | | 技能市场 | `/admin/waiguan/skillmarket`,skill:read,实时展示当前用户授权的 M06 published 固定版本并分页刷新;发布不代表运行可用。BFF 只经 M01 `/api/v1/skills/published`,不直连 SkillHub。注册、M02 审批与发布维持独立 `/admin/waiguan/skills`;旧 `/skillhub` 跳转本地市场。 | ## 历史验证状态(对应各条记录日期) **确认处罚意见:手选档位不被判定结果覆盖(2026-10-14)**:分析量罚情节的「保存并重新识别」照旧调裁量判定(命中情节要写进处理意见第一段),但页面已选的标准一律采用页面所选的(`grade_status=manual_confirmed`),只有认不出的档位(旧全称/错字)才采用判定结果;判定与确认都不推进步骤。处理意见三段(情节认定 / 建议按「档位」处罚 / 责令限期改正+罚款),金额大写小写由 `penalty_amount.amount_upper`/`amount_rmb` 代码算,不让模型写;第三段只看有没有金额,查不到裁量区间也照写。报告书下载由 markdown 改回 `.docx`(`GET .../inspection-report.docx`)。模型通道失败归一层的 `error_code` 必须带到页面原因:`answer` 空不等于内容无法识别,凭据过期要走 `_conduct_failure_reason(code=...)` 报「大模型服务鉴权失败」(契约 v13)。 **汇智助手自己查库(2026-10-14)**:取消「引用外汇管理知识库」开关与外管侧预检索注入(删 `_kb_snippets`/`_kb_*` 事件字段)。政策库、法规库、案例库由 deer-flow 容器内的 M03 MCP 服务自己检索,外管只转发问题。对话页改为:Enter 发送 / Shift+Enter 换行、输入框自适应、研究过程 `
` 折叠、整页滚动跟随、回答可复制、消息去气泡。 **条款标注历史演进(2026-10-04)**:此前采用行首规则切分和分片长度约束。现行自然单元、确认覆盖与摘要规则以 `docs/scenarios/clause-subject-domain-annotation.md` 为准;不能把历史 1000 字切片限制恢复为当前自然条款上限。列表「已标注」仍表示条款已生成且全部人工确认,不是新的政策状态。 **工作台待完善待办不再打开空列表(2026-09-24)**:「N 项政策待完善」的数字统计当前用户尚未完成的录入项,不是政策草稿。已发布或已失效政策的源录入项不再计入。工作台卡片和待办都打开 `/admin/waiguan/policy/library?completion=pending`。政策列表在该筛选下返回同一批录入项:已有关联政策的打开政策详情,尚未建政策的显示文件名和状态,并可确认入库或取消。关键词只匹配这些待完善项。不改 M01–M09。 **各流程状态以后台作业为准(2026-09-24)**:用户反馈「最终各流程状态应以后台状态为准,不是以前记录的未同步状态」。本地 SQLite 是投影/缓存,不是 M01 作业真相源。此前 GET 详情/列表/档案直接吐本地快照,且本地一旦落到失败就再也不问后台——上一次 HTTP 超时被打成失败、而后台其实成功或仍在跑的记录会一直显示失败。本轮把六条异步作业的回写口径统一为「以后台为准」: - **回写触发**:进行中(pending/extracting/queued/running/generating)照常轮询;**本地失败但仍带后台作业 id**(识别 `recognize_task_id`、知识化/词条/案例 `import_id`、文稿 `manuscript_invocation_id`、文书生成 `generate_task_id`)也再问一次后台。成功则覆盖本地,后台仍在跑则回到进行中继续轮询,后台仍失败则保持失败。已成功态不每次 GET 再打网关。 - **GET-heal**:`get_policy` / `list_policies` / `get_archive` / `list_wiki_pages` 读取时调用 `PolicyService.sync_backend_jobs`(识别+文稿+知识化)、`refresh_case_library`、`refresh_generation`、`WikiService.refresh_knowledge` 回写;列表只对进行中/失败带 id 的条目并发短超时(15s)愈合,已定稿政策不逐条打网关。 - **没问到后台不改写终态**:GET task/import/manuscript 传输失败时保持本地态、不空转版本;只有拿到后台可解析的终态才落库。政策生命周期 `Policy.status`、档案审批仍是本地业务机,不受此同步改写——只同步 M01 异步作业子状态。 - **案例库补前端轮询**:档案详情新增 `scheduleCasePoll`(4s,`case-library/refresh`),此前案例库导入无 UI 轮询。 - mypy + 227 tests 全绿。不改 M01–M09、不改 BPMN、不拉长 HTTP 超时、知识图谱仍不组进 payload。 **识别中重新识别(2026-09-24)**:未命名草稿点「重新识别」刷不出要素,不是按钮没打到后端(retry/refresh 多次 200)。识别项停在 EXTRACTING 且带着旧失败码;流程定义无 EXTRACTING 自环,再点会被同态推进拦住;轮询对空/未知/传输错误不回写终态,页面永远「正在识别」。现:识别中也可重试(换新任务、不清 BPMN);`refresh-recognition` 把失败/空 status/传输错误写成失败(GET task 15s);详情失败横幅带 `recognition_error`。mypy + 221 tests 全绿。不改 BPMN、不改 M01–M09。 **录入并入政策库·「添加政策 → 详情页」单一流程(2026-09-23)**:删除独立「政策智能录入」页与录入项详情页,收敛为一条流—— - **上传即建 draft 政策并跳详情**:政策库「添加政策」上传原文(`POST /api/policies` multipart)→ 复用 intake 上传机器建 `IntakeItem`+M04 资源 → **立即建 draft 政策**(要素空,`source_item_id` 关联录入项)→ **先识别转换文稿** → 用户核对/改正文并「确认文稿」后再触发 M02 识别(吃已确认 markdown 文稿 URI,不打原文)→ 前端跳 `/detail/{id}`。转换在后台跑;确认前不识别。 - **详情页承载复核确认**:详情 DTO 出 `recognition_status`(从 source item 派生 waiting/extracting/ready/failed/none)、失败原因 `recognition_error` 与 `manuscript_confirmed`。draft 态显示识别横幅:转换中提示先核对文稿;文稿已转换未确认引导点「确认文稿」;确认后 waiting 才轮询 `POST /refresh-recognition`(就绪则把候选要素回填进政策,不覆盖用户已改值);**失败或识别中均可** `POST /retry-recognition` 重识别(识别中换新任务、不走流程自环);轮询把失败/空 status/传输错误写成失败,页面停转圈;就绪后用「编辑基本信息」核对 →「确认基本信息」`POST /confirm-metadata`(draft→metadata_confirmed + 服务端校验全部必填 + **重写文稿 YAML 头**,不再启动转换)。 - **八步投影(2026-09-23)**:上传原文 → 识别转换文稿 → 确认修正文稿 → 智能识别要素 → 复核确认要素 → 确认基本信息 → 知识化入库 → 发布政策。`project_policy_flow` 据 `manuscript_ready` + `manuscript_confirmed` + `recognition_status` 推进——文稿未就绪当前步是转换;已转换未确认当前步是确认修正文稿;waiting 且已确认→recognize 当前步;extracting→recognize 当前步;ready→review 当前步;none→转换确认后识别/复核均完成。存量识别已启动/完成或已确认元数据视为已过确认闸。 - **侧边栏删「政策智能录入」**;`intake_page`/`intake_item_page` 路由 + `policy_intake.html`/`intake_detail.html` 模板删除。`intake_service` 识别机器保留作后端复用。工作台「待完善政策」与待办「N 项政策待完善」打开 `/admin/waiguan/policy/library?completion=pending`,列出当前用户尚未入库的录入项;已有政策的打开详情,没有政策的可在列表确认入库或取消。 - 全套 199 tests + mypy 全绿;真机端到端验证上传→draft→转换文稿→确认文稿→识别通链。 **政策库流程优化·多库+图谱+详情页承载全流程(2026-09-23)**:政策库五块重构落地—— - **知识化多库**:`submit_to_knowledge` 由单库二选一改为**多选库**。每库一次 import,源按库拆分 `ksrc___` 互不覆盖;明细存 `Policy.knowledge_targets`(JSON),`knowledge_status/knowledge_collection` 由明细派生保持向后兼容。逐库轮询聚合、按目标(某库/全部)撤回。提交中立即锁按钮防连点;`pending` 时同一槽位改「正在提交知识库…」并在知识化卡提示导入时间可能较长。未入库显示「提交知识库」,已入库同一槽位变成「取消知识库」并另增「重新提交知识库」。**知识图谱暂未开通**:实测 M01 网关 `create_policy_knowledge_import` 白名单**不接受** `extract_graph`(未知字段整单 400 `INVALID_CONTEXT`),故 payload 不带该键、不记 graph 伪目标,详情页图谱复选框禁用标「暂未开通」;待网关白名单 + M05 消费联验通过后再放开。同理 `doc_number/issuing_authority`(基本信息附着)也因白名单未纳入而暂不透传。 - **详情页承载全流程**:顶部按本政策投影**八步流程图**;修复深链锚点漂移(`policy_flow` 深链 `#manuscriptCard/#knowledgeCard/#lifecycleCard` 此前指向不存在的 id)——文稿卡改名 `manuscriptCard`、要素卡挂 `lifecycleCard`、新增知识化卡 `knowledgeCard`,`hashchange` + 首屏滚动定位。 - **详情页能力补齐**:下载原文(`GET .../original`)、下载 markdown 文稿(`GET .../manuscript/download`)、编辑基本信息内联表单(PATCH `update_metadata`,日期可手输+校验、效力状态落库)、多库/图谱推送撤回。 - **列表页瘦身为状态视图**:条目名称与状态徽章各占一行;展示当前节点(八步) + 状态 + 编入哪些库/图谱徽章。发布按钮在详情页。 - **编辑基本信息表单(2026-09-23)**:日期可手输 `YYYY-MM-DD` 或点日历按钮(`.wg-date` + 原生 `showPicker`),非法日期(含 `2024-13-40`)标红拦截;失效日期空值默认 `2099-12-31`(保存空串/确认时服务端同样落该默认);新增效力状态字段(有效默认/失效,落库 `effect_status`,YAML 头写中文「有效/失效」,**不联动**生命周期 `status`);必填项(政策名称/发布机关/效力等级/生效日期/适用地域)红星 + 空值红框。活库经迁移 15 + `ensure_policy_manuscript_schema` 幂等补列。 - **删适用对象录入**:从政策级要素展示移除(录入识别表单本就不含它),保留 DB 字段供未来条款级识别写入。 - **基本信息随切片附着**:知识化 payload 补 `doc_number/issuing_authority`,底座 chunker 复制到每条 chunk(发布机关能否随检索副本还原取决于 M05 控制面白名单,属跨底座依赖)。 - 全套 174 tests + mypy 全绿;场景手册 `docs/scenarios/policy-knowledge-collection.md` 同步重写为多库+图谱契约。 **汇智助手对话归属(2026-09-24)**:历史气泡跳过隐藏记忆注入,每一轮只显示该轮问题和回答。工具调用失败归一为 `WAIGUAN.M03_TOOL_LIMIT` 或 `WAIGUAN.M03_PROVIDER_AUTH`,页面只显示中文原因。助手账号全站共用,外管用本地归属表 `wg_deerflow_thread` 和线程标记按登录用户分开;旧的无主对话不展示。页面有「保存对话」;点击退出登录时先调用 `POST /admin/waiguan/api/deerflow/discard`,只删除当前用户未保存的对话,已保存的保留。刷新或关闭页面不触发清理。 **汇智助手聊天修复(2026-09-21,其中「已引用知识库 N 条」一段已被 2026-10-14 的 M03 自主检索取代:外管侧预检索与引用计数一并删除)**:区①「已引用知识库 N 条」改为过滤后的真实相关条数,不再把检索 `top_k` 凑满值当引用数;无关问句提示未注入知识上下文。模型通道连接拒绝不再把英文 `Errno 111` 原文打到气泡,归一为 `WAIGUAN.M03_PROVIDER_UNAVAILABLE`(「大模型服务暂时连不上」)。运行侧须保持宿主机到容器的代理转发(`~/m04-stack/launch_deerflow_proxy_forward.sh up`),零改 deer-flow 源码。 **汇智助手页面收敛(2026-09-19)**:`/admin/waiguan/deerflow` 现在只保留两块:`汇智助手对话`(模式、附件、新建对话、流式研究)和 `进行中对话`(状态、更新时间、继续对话、重命名、删除)。重命名通过 `POST /admin/waiguan/api/deerflow/thread/{id}/rename` 的表单 `title` 调用现有对话状态接口,删除通过 `DELETE /admin/waiguan/api/deerflow/thread/{id}`,均复用 `deerflow:use`,删除前端二次确认、后端 404 幂等;冲突统一为 `WAIGUAN.M03_CONFLICT`。视觉继续使用 `#409eff` 主色、无投影卡片、`WG.badge` 中文状态字典;空对话使用紧凑提示条,已有消息在独立区域滚动,避免长对话把输入区推离视口。 已验证:登录流程(303 + 会话 cookie)、业务编排页面与 definitions/runtime 只读接口、8 个页面登录态 200、数据接口正确经 M01 转发并**如实透传底座错误码/trace_id**。 **业务编排(2026-09-19,第一增量)**:新增 `/admin/waiguan/workflows` 只读页面,拓扑使用稳定左到右 SVG 分层布局;智能执行点、人工门、终态、退回/取消边均以业务语义展示。政策录入、政策库生命周期、档案审批、违法事实分析四条拓扑均从独立外部流程定义投影。`/api/workflows/definitions` 只输出脱敏业务拓扑,`/api/workflows/runtime` 从本地业务表聚合状态计数并沿用档案机构范围隔离;节点点击只跳转现有列表筛选,人工确认/取消仍在业务详情页完成。界面沿用 `#409eff` 主色、浅蓝侧栏、无投影和 `WG.badge/statusLabel` 中文化约定。 **业务编排·底座真实编排(2026-09-19,第二增量)**:同页在两条业务派生拓扑之后加性新增「底座真实编排」块,消费底座 M01 只读脱敏端点 `GET /api/v1/workflows`(经 BFF `GET /admin/waiguan/api/workflows/foundation` 中转,守卫复用 `archive:read`,token 已含 `workflow:read`)。`gateway.list_workflow_definitions()` 取封套直接数组,路由 `_foundation_topology_payload()` 以**白名单构造**映射为拓扑(只保留 `step_order/step_name/step_type/max_retries`,human_gate 另加中文权限提示与截断的人工提示),底座实现字段一律不透传(防御式脱敏,含金丝雀门禁 `tests/test_navigation.py`)。节点视觉词汇仍只有普通/智能执行/人工门三类,底座块只读不跳转,边按 `step_order` 线性串联。该块加载失败(如缺 scope 403)局部降级不拖垮业务拓扑。底座真实拓扑单独走 `foundation` 端点,**不混进** `definitions`。 **汇智助手(deer-flow M03)三区整合(2026-09-17 完成,2026-09-18 提升为一级「汇智助手」入口)**:deerflow.html 三区重构全部落地,5 门禁全绿(mypy/test_contracts/test_docs_sync/test_permission_config),真机重启验证 HTTP 200。 - **区③参数与入口**:mode 单选 + 引用知识库开关 + 历史工作台入口使用外部地址;当前技能市场只进入本地 `/admin/waiguan/skillmarket`,不配置 SkillHub 外部地址。 - **区②完整工作台(C 档 iframe + 登录桥)**:`_same_site_embeddable()` 正确剥端口判定同 site;`wg-df-embed`/`wg-df-iframe`/`wg-df-embed-loading`/`wg-df-embed-fs` CSS 类完整;JS 实现加载态遮罩、重载按钮、全屏切换(`requestFullscreen`+`fullscreenchange`)。内网 IP 部署(`192.168.1.3:18500` 外管 + `192.168.1.3:12026` deer-flow 反代)同 site → csrf_token 流通 → iframe 可嵌。**登录桥「打开即已登录」(2026-09-17 新增,专属应用账号,零改 deer-flow)**:`bridge_available`(同 site + 已配应用账号)时前端先 `POST /api/deerflow/session` → `deerflow_client.mint_browser_session()` 用专属应用账号(`waiguan-app@…`,"为不同应用分别注册不同用户")调 deer-flow **自身公开** `login/local` 换会话 cookie → BFF 以 **host-only**(不带 Domain)cookie 种给浏览器(同 IP 不同端口=same-site,cookie 忽略端口)→ iframe 打开即已登录,无需框内再登。守卫顺序=同 site(否 `WAIGUAN.M03_BRIDGE_CROSS_SITE`)→ 已配账号(否 `WAIGUAN.M03_BRIDGE_UNCONFIGURED`)→ 登录(失败 `WAIGUAN.M03_BRIDGE_LOGIN_FAILED`),三者均优雅回退新标签深链、绝不空转/泄漏。**密码只在服务器侧 0600 文件,绝不进浏览器/日志/异常**。**固有取舍**:外管所有用户在内嵌 deer-flow 共用这一应用身份(应用级审计),终端用户级权限/审计仍由外管 `deerflow:use` 门 + 外管审计承担。运维供给:`~/m04-stack/provision_deerflow_app_account.sh`(调公开 register 建普通 user 账号,凭据写 0600 文件)。**真机验通**:注册 201 → `login/local` 返 `Set-Cookie: access_token; SameSite=Lax` → `/me` 确认身份。 - **区①快捷研究(外管风格多轮流式研究台,2026-09-17 深化)**:从单次问答升级为**外管原生多轮流式对话**——`POST /api/deerflow/stream` → `run_stream()` → deer-flow `/api/runs/stream`(SSE)。BFF 把 deer-flow 原始 SSE(`event: metadata`/`values` 全量快照)解析成干净事件 `{type: kb|meta|answer|done|error}`;前端 `fetch`+`ReadableStream`(EventSource 不支持 POST 故用 reader)消费,`wg-msg` 用户/机器人气泡渐进式渲染,`thread_id` 维持多轮续接、`AbortController` 支持停止、`新对话` 重置线程、Ctrl/⌘+Enter 发送。**真机验通**:flash 模式 `kb→meta→answer→done` 全序列贯通、多轮追问复用 thread_id 续接成功;服务不可达/AbortError 中文降级不空冒充。同步单次 `POST /api/deerflow/run`→`run_wait()` 保留兜底。核心动因=用户反馈「打开 deer-flow 还要注册、看到的是 deer-flow 页面、不像外管项目」→ 常见研究任务留在外管原生 UI,deer-flow 原生界面降级为区②可选高级入口。 - **LAN 门户反代(形态③)**:`waiguan-portal-nginx` host-network 容器 `:18500/12026/80` 全通;`_same_site_embeddable` 端口剥离 bug 已修;4 入口全 200 无 127.0.0.1 泄露。 **区①深化:进度时间线 + 历史续接 + 产物下载(2026-09-18,均 PAT 白名单零改 deer-flow)**:把区①从「单一渐进答案气泡」深化为自足研究台,5 门禁全绿(mypy/test_contracts/test_docs_sync/test_permission_config/test_intake_flow)。 - **更丰富的流式进度**:`run_stream()` 请求体带 `stream_mode=["values","custom"]`;`custom` 路 `task_started`/`task_failed`→`_custom_step()` 归一成子任务过程条,`values` 快照 `messages[].tool_calls`→`_tool_steps()` 按 call id 去重、英文工具名经 `_TOOL_LABELS` 转中文;SSE 干净事件扩为 `{type: kb|meta|step|answer|interrupt|done|error}`,前端渲进答案气泡上方 `.wg-flow-steps` 时间线。进度为启发式过程可视化(deer-flow 无刚性阶段机)。`interrupt` 事件已预留(前端仅提示转完整工作台),HITL `command.resume` 闭环下一轮。 - **历史研究浏览 + 续接**:`get_thread_state()` 调 `GET /api/threads/{id}/state` 取 `values.messages[]` 归一成 me/bot 气泡;「我的研究运行」卡「恢复对话」按钮 → 渲进区① → 设 `dfThreadId` → 沿同线程续问。 - **报告/产物下载**:`download_run_artifacts()` 先 `_latest_terminal_run_id()`(`GET /api/threads/{id}/runs` 取最新终态、优先 success)服务器侧解析 run_id(浏览器从不接触),再 `POST /api/threads/{id}/runs/{rid}/artifacts/archive` 取 zip;BFF `GET /api/deerflow/thread/{tid}/artifacts` 端点 Content-Disposition 用 RFC5987 `filename*=UTF-8''` 避免中文名 latin-1 崩溃;非终态 run(409)/无产物 → 中文降级 JSON 不下发坏文件。所需 PAT scope=`threads:read`+`runs:read`+`runs:create`(均在 deer-flow PAT_ALLOWED_SCOPES,联调 PAT 若缺 `runs:read` 需重铸,仍零改 deer-flow)。**待真机复验**:进度时间线/历史恢复/产物下载三项端到端 + 停 deer-flow 降级不空冒充 + `git -C deer-flow status` 干净。 **汇智助手入口合并(2026-09-18)**:原对接底座 M01 智能问答(`/api/v1/assistant/*` 轮询式问答)的独立「汇智助手」页已下线,其菜单名「汇智助手」改指 deer-flow 落地页(`/admin/waiguan/deerflow`);原「智能中枢」Dropdown 拆除,「智能研究」并入「汇智助手」、「技能市场」提升为一级菜单。删除:`assistant.html`、`waiguan.py` 三个 `/api/assistant/*` 端点、`gateway.py` 五个 M01 assistant 方法、两个旧 assistant 测试。`knowledge:search` 权限点为多页共用,保留。 **区①支持文件上传 = 实现 deer-flow 全功能(2026-09-18,统一应用账号会话身份,零改 deer-flow)**:给区①快捷研究台加附件上传,补齐「全功能」。5 门禁(mypy/test_contracts/test_docs_sync/test_permission_config/test_intake_flow)在改后复验。 - **实测确立的硬约束**(本轮真机验证,非假设):① deer-flow PAT **不在上传路由白名单**(`pat.py::_PAT_ROUTE_RULES`)→ PAT 打上传 403;② PAT 属主 **≠** 区②登录桥用的应用账号(用 app-cookie 建的 thread 用 PAT `GET` 返 404);③ 上传按「用户+thread」隔离(落盘 `users/{uid}/threads/{tid}/user-data/uploads/`,agent 只看得到同用户同 thread 的文件,虚拟路径 `/mnt/user-data/uploads/{name}`)→ **带文件的研究必须整条(建 thread→上传→run/stream)走同一用户**;④ 应用账号会话可完成全链(`login/local` 表单登录种 access_token+csrf_token cookie,`create thread`/`GET uploads/limits`→10 文件/50MB/100MB/`POST uploads` multipart `files`+`X-CSRF-Token` 双提交均 200)。 - **采用决策**:区①**所有**服务器侧 deer-flow 调用(list_runs/run/stream/thread state/artifacts/**upload**)统一改用**应用账号会话 cookie(access_token JWT + csrf_token)取代 PAT**——这是唯一能让区①「全功能 + 与区②同身份」的方案(上传身份对齐是硬要求),顺带消除旧区①(PAT)与区②(app 账号)运行历史割裂。会话按需铸造缓存、401/403 自动重铸一次(`_ensure_session`/`_invalidate_session`);`mint_browser_session()` 公共契约不变(薄封装取 access_token)。 - **实现**:`deerflow_client` 新增 `create_thread()`/`upload_files()`/`upload_limits()`,`_login_session`/`_csrf_headers`/`_session_cookies` 承载会话+CSRF 双提交;`run_stream`/`download_run_artifacts`/`request()` 全部改走会话 cookie。新端点 `POST /api/deerflow/upload`(`deerflow:use` 门,服务端 limits 兜底校验,无 thread 先建)。`deerflow.html` 区①加「附件」按钮+隐藏 file input+chips 区(中文名/大小/可移除),JS 客户端预校验后 `POST upload` 拿 `thread_id`→渲染 chips→发起研究沿同 thread 透传。错误码 `WAIGUAN.M03_UPLOAD_LIMIT`/`M03_UPLOAD_HTTP`/`M03_UPLOAD_TRANSPORT`(error-codes.md v5)。 - **增益**:上传是**服务器侧 BFF 中转**(浏览器只与外管交互,deer-flow 细节不外泄),故不受区② iframe same-site 限制 → **LAN 与远程隧道都能用**。**密码只在服务器侧**(`~/m04-stack/deerflow-app-account.txt` 0600 或 env),绝不进浏览器/日志/异常。**待真机复验**:区①选文件→chip→研究引用附件内容 + 区②同 thread 可见该文件 + 远程隧道下同样可用 + 中文文件名。 **前端 Markdown 只读渲染统一走 markdown-it(2026-10-19)**:政策文稿、汇智回答和引用原文走 `WG.renderMarkdownInto`。内部是离线 markdown-it 14.1.1 加中文标点插件,公共页头引 `/static/vendor/markdown-it/markdown-it-cjk.min.js`,结果再过 DOMPurify。中文标点紧贴 `**` 时显示加粗,不改写原文。库不可用回退纯文本转义。编辑页仍用完整 Vditor。列表页不渲染 Markdown。门禁 `tests/test_navigation.py`。 用户设置支持多个角色。管理员维护的角色按行保存,系统将角色模板权限与显式权限计算为稳定去重并集;普通用户只能维护自己的机构和职务,不能通过接口修改授权字段。兼容历史单角色资料,升级旧管理员库时会自动补齐多角色列。 **权限强制(2026-09-15)**:角色权限点不再只是展示,已由 `app/services/authz.py` 的 `require_perm(perm)` 依赖挂到写操作路由强制校验,缺权限返回 403 `WAIGUAN.FORBIDDEN`(结构化,带 `permission` 字段)。超级管理员(`username==ADMIN_USERNAME`)恒放行。已挂载:政策发布/失效→`policy:publish`、政策删除→`policy:delete`、提交/取消/重新提交知识化→`knowledge:import`(部门主要负责人及以上)、新建单位→`org:create`(部门负责人及以上;改删机构仍是 `permission:manage`);档案新建/上传文档→`archive:write`、档案删除→`archive:delete`、**档案复核退回→`archive:approve`**;**档案推进(advance)按当前态动态判权**——DRAFT 送审需 `archive:write`,SUBMITTED/REVIEWING/APPROVED/CLOSED 的领受/审批/办结/归档需 `archive:approve`(不用静态 `require_perm`,在路由体内按 `perm_for_advance(当前态)` 判 `has_permission`,缺权限抛 `PermissionDenied`→全局处理器转 403)。前端 `WG.can()/WG.ensurePerms()`(`wg-ui.js`)据当前用户有效权限隐藏对应按钮,与后端门禁一致(推进按钮也按 DRAFT=write/其余=approve 分别判定)。角色模板见 `profile.py::ROLE_PERMISSIONS`(5 角色:业务人员/部门负责人/部门主要负责人/单位负责人/单位主要负责人,18 权限点),默认角色为最小权限的"业务人员",中文标签见 `PERMISSION_LABELS`。 **权限系统全面收口(2026-09-15)**:在上述基础上做了四件事,消除强制模式不一致与门禁盲区—— - **新增 `policy:create`(添加政策)权限点(2026-09-24)**:`POST /api/policies` 从 `policy:write` 拆出。编辑、确认、去重仍用 `policy:write`,五个角色都有。添加政策默认只有「单位主要负责人」。政策库页右上角和空列表按钮按 `WG.can("policy:create")` 隐藏;没有权限直接调用接口返回 403。超管仍放行。权限点共 18 个。 - **统一设置类端点强制模式**:`app/routers/waiguan.py` 原用体内 `_permission_admin()`+`_permission_denied()`(返回 403 `PERMISSION_DENIED`),与其余路由的 `require_perm`(403 `WAIGUAN.FORBIDDEN`)两套模式两个错误码。已全部改为 `require_perm` 依赖,删除这两个辅助函数(`grep _permission_admin` 零残留)。 - **`user:manage` 与 `permission:manage` 真正分离**:此前用户管理端点实际都查 `permission:manage`,`user:manage` 是死代码。现在**用户管理**(`/api/settings/users*`、`/roles`)→ `user:manage`,**机构目录管理**(`/api/settings/organizations` 的写/查子项)→ `permission:manage`。保持开放:`GET /organizations`(机构树被新建用户弹窗/档案检查人选择器复用)、`GET/PUT /profile`(管自己)。 - **裸奔写操作补挂权限点**:档案的封存/文档复核/纳入案例库/文书生成→`archive:write`;政策的建批次/上传/重试/改要素/确认入库/取消/删除录入项/改元数据/确认元数据/去重裁决/图谱重建→`policy:write`,`resolve`(只读查询)→`policy:read`;知识检索/案例检索/助手会话→`knowledge:search`;资源上传/任务重试取消→`policy:write`。仅轮询下游状态的端点(`refresh`/`refresh-knowledge`/`refresh-generation`/`case-library/refresh`)保持开放,避免破坏只读查看者的自动轮询。 - **前端按钮门禁扩展**:`WG.can()` 门禁从原来 3 处(政策 review/publish/delete)扩展到全部写操作入口——档案详情(封存/案例库/文书生成/通过复核按 `archive:write`)、设置页(用户写操作按 `user:manage`)、机构目录(增删改按 `permission:manage`)、政策录入(上传/重试/删除按 `policy:write`,无权限禁用上传区)、政策详情(确认元数据/去重裁决/图谱重建按 `policy:write`)。后端门禁是权威,前端只隐藏入口。 - 回归测试新增 `_permission_matrix`(纯函数测 `effective_permissions`/`permission_allows` 的角色矩阵)。真机验证:业务人员建录入批次 200、推进档案/列用户/建机构分别被 403 精确拦截。 **防提权(2026-09-15,随 user:manage/permission:manage 分离补上)**:分离后可能存在"有 user:manage 无 permission:manage"的角色,而建/改用户接口直接接受 body 里任意 `roles`,若不校验会让这类用户把含 `permission:manage` 的高权角色赋给他人/自己——经典的"授予自己没有的权限"提权。`app/services/authz.py::ensure_can_grant_roles(request, roles)` 强制:**所授角色的权限并集必须 ⊆ 操作者自身有效权限**,否则抛 `PermissionDenied`→403(超管豁免)。已接入 `POST/PUT /api/settings/users`。回归测试 `_grant_roles_no_escalation`。真机验证:仅持 `user:manage` 的操作者赋"业务人员"200、赋"单位主要负责人"(含 `permission:manage`)被 403。 **用户管理机构范围隔离(2026-09-15,与档案隔离对称)**:档案已按"本机构+下级"隔离,用户管理原先却对全表开放——`user:manage` 管理员能列/查/改/删/重置任意机构用户。补上:`app/routers/waiguan.py::_ensure_user_in_scope(request, target_org_id)` 复用 `org_scope.visible_org_ids`,操作者只能管本机构+下级用户(超管/无机构约束者管全部)。接入全部用户管理端点——列表默认按可见范围过滤(显式 `organization_id` 筛选时与可见范围取交集,防越权探测);查/改/删/重置密码对范围外用户返回 404(不泄露存在性);建/改用户不能把用户建到/移到范围外机构(返回 403)。回归测试 `_user_mgmt_org_scope`。真机验证:四川分局(org=2)的 `user:manage` 管理员看不到/管不了陕西(org=7)用户(列表不含、查改删重置均 404、建 org=7 用户 403),但能管本机构用户;超管不受限。旁路 `GET /api/settings/organizations/{id}/users`(按机构查用户)同样与调用者可见范围取交集,防止绕过隔离查范围外机构用户。 **机构写操作范围隔离(2026-09-15,数据隔离对称收尾)**:机构管理的写操作(建/改/删)原先对全表开放——各单位的 `permission:manage` 管理员(如四川、陕西各自的"单位主要负责人")能互相编辑/删除对方的机构节点。补上 `_ensure_org_in_scope(request, target_org_id)`(与用户隔离同口径):非超管只能维护本机构+下级;create 校验拟挂的 `parent_id` 在范围内(顶级机构 `parent=None` 仅超管可建),update/delete 对范围外机构返回 404。**`GET /organizations` 保持开放**(被新建用户弹窗/档案检查人选择器复用,且机构树非敏感)。回归测试 `_org_write_scope`。真机验证:四川分局(org=2)的 `permission:manage` 管理员改本机构 200、改/删陕西(org=7) 404、建本机构下级 200、建顶级/建陕西下级均 403。 **审计接口权限门补挂(2026-09-15)**:`GET /api/audit`(读全量审计事件)原先**无任何权限门**,任何登录用户(含业务人员)都能读全系统操作轨迹——`audit:read` 权限点早已定义、仅分配给单位负责人/单位主要负责人,却从未接到路由,是"权限点定义了却没接"的典型漏洞。已挂 `require_perm("audit:read")`;前端审计页 `audit.html` 加载先 `WG.ensurePerms()`,无 `audit:read` 时显示"无审计查看权限"提示并禁用刷新/时间窗控件,不再直接发请求吃 403。真机验证:业务人员读审计 403、超管 200。 **前端机构树按节点裁剪(2026-09-15)**:`GET /organizations` 有意返回全树(选择器数据源),但机构设置页原先对每个节点都渲染增删改按钮(仅按 `permission:manage` 整体门禁),越权节点上会出现点了必 404 的死按钮。`GET /api/settings/profile` 新增 `manage_all_orgs`(超管/无机构约束=true)与 `manageable_org_ids`(本机构+下级 id 集),`directory.html` 据此 `canManageOrg(id)` 逐节点裁剪增删改按钮;"新增顶级机构"仅 `manage_all_orgs` 者可见(与后端 create parent=None 仅超管一致)。用户列表前端天然安全(数据源 `list_users` 已按范围过滤)。 **机构目录树层级折叠(2026-09-15)**:机构树原先全展开平铺(缩进靠 `padding-left: depth*px`),层级深时一屏铺满、难以定位。三处渲染机构树的地方统一改为**嵌套容器缩进 + 逐节点折叠开关**:有下级的节点前显示可点箭头(`fa-chevron-down`/`right`),叶子节点用等宽占位保持对齐;子级包在 `.wg-org-children`(`padding-left:20px`)里,折叠即 `display:none`。折叠状态用各自的 `Set` 记住(`collapsedOrgs`/`collapsedPickerOrgs`/`collapsedInspectorOrgs`),重渲染保留;**搜索时强制展开**保证命中的后代可见。三处:①机构设置页 `directory.html`(管理树,额外加"展开/折叠"全局按钮);②新建用户弹窗 `settings.html` 的机构选择器;③检查档案 `archives.html` 的检查人选择器。默认展开(保持原行为),纯前端改动,样式集中在 `waiguan.css` 的 `.wg-org-toggle`/`.wg-org-children`。 **新增用户注册优化(2026-09-15)**:新增用户弹窗几处体验/一致性问题一并收口,均为前端改动(后端 `validate_user_payload`/`ensure_can_grant_roles`/`_ensure_user_in_scope` 仍是权威): - **角色只列"可授予的"**:原先列出全部 5 个角色,低权限操作者选了高权角色提交必被后端 `ensure_can_grant_roles` 挡回 403(死选项)。前端新增 `canGrantRole(role)`(该角色权限 ⊆ 操作者自身权限,超管例外,与后端同口径)+ `renderGrantableRoles()`,只渲染可授予角色,并提示"另有 N 个更高权限角色因超出你的权限范围而不可授予"。`wg-ui.js` 新增 `WG.perms()`/`WG.isSuper()` 暴露操作者权限集。 - **角色权限实时预览**:勾选角色即时显示该组合的权限并集(复用改角色弹窗的 `renderRolePerms`),建号前就能看清授权范围。 - **密码可视 + 一键生成**:初始密码框加"显示/隐藏"眼睛与"生成强密码"按钮(`genPassword()` 出 14 位、大小写+数字+符号各至少一个、洗牌),生成后自动明文显示便于记录转交。 - **密码强度规则前后端一致**:占位提示原写"含字母和数字",但前后端都只校验长度≥8(提示说谎)。改为**后端 `validate_user_payload` 强制"长度≥8 且同时含字母和数字"**(挡住 `12345678` 类弱口令),前端预校验同口径镜像。后端校验条件放宽为"需设密码或传了密码才校验",改资料不传密码时正确跳过;重置密码走独立生成器(直接设值,不过此校验)。 - **前端预校验**:提交前按与后端同口径的正则校验登录名/姓名/密码长度/角色必选/电话/邮箱,一次性列出所有问题,替代提交后逐条 400。 - **表单完整重置 + 防重复提交**:修复"复用弹窗残留上次输入"(原先只重置机构,登录名/密码/角色会留存);创建按钮提交期间禁用防连点。 **改角色弹窗一致性(2026-09-15,与新增用户同口径)**:改角色弹窗原先 `roleCatalog.map(...)` 全量渲染,同样有死选项问题,且多一个后端陷阱——`ensure_can_grant_roles` 校验**整份 roles 载荷**,故被改用户若持有操作者无权授予的高权角色,操作者提交完整列表(哪怕没动那个角色)也会被 403,等于根本改不了这个用户。当前潜伏(唯一有 `user:manage` 的"单位主要负责人"恰好能授予全部角色),但显式授予 `user:manage` 即可造出低权管理员触发。修复:可授予角色正常勾选;被改用户已持有、但操作者无权授予的角色显示为**锁定只读**(`disabled` + 🔒 图标),存在锁定角色时**禁用保存并说明"你无权修改该用户角色"**(诚实反映后端会 403,而非提交后吃错误);既不可授予、用户又未持有的角色直接隐藏。`effectiveRoleSelection()` 把锁定角色并入提交载荷(`disabled` 不进 `checkedRoles`),避免误删既有角色。真机验证:低权操作者(业务人员+显式 user:manage)提交持有"单位主要负责人"用户的完整 roles → 后端 403,证实前端锁定+禁用保存的设计正确。 **检查档案数据范围隔离(2026-09-15,本机构+下级可见)**:权限点管"谁能做什么操作"(粗粒度),本项管"谁能看到哪些数据"(细粒度)。**只对档案做**——档案是机构敏感案卷,平级不该互看、上级可看下级;**政策不做**(政策是法规知识库,跨机构共享检索正是其价值,`git diff` 政策代码零改动)。实现: - `InspectionArchive` 加 `organization_id`(裸整数列,index,null 允许)。**机构表 `wg_organization` 在 admin 库、档案在 business 库属跨库,故不能做外键**,只存整数。迁移 `migrations/biz/4_*_add_archive_organization.py`(幂等,手写规避 aerich 交互卡顿)。 - 新建 `app/services/org_scope.py`:`visible_org_ids(request)` 返回当前用户可见机构 id 集合(本机构 + `_subtree_ids` 递归所有下级);超管或无机构约束的管理员返回 `None`=看全部。 - 创建档案时 `create_archive` 经 `_actor_org(request)` 捕获创建者机构写入 `organization_id`。 - `list_archives` 服务层加 `org_ids` 参数(`None`=不限,否则 `organization_id__in`);路由注入 `visible_org_ids`。 - 详情 + 所有按 archive_id 的读写接口(get/advance/reject/legal-hold/documents/case-library/generate/transitions/delete)经 `_ensure_visible(request, a)` 守卫,**越权抛 `NotFoundError`→404「档案不存在」,不泄露跨机构档案的存在性**(不是 403)。 - 回归测试 `_archive_org_scope`(测 service 层 org_ids 过滤契约)。真机验证:四川分局(org=2)用户列表只见本机构档案、访问本机构档案 200、访问陕西(org=7)档案 404、超管见全部。 **删除 B 类文书生成功能(2026-09-27)**:辅助生成只保留五类走本地 Word 模板成文的文书(执法检查报告书 / 行政处罚立案审批书 / 建议书 / 通知书 / 决定书)。**检查通知书 INSPECTION_NOTICE、行政处罚告知书 PENALTY_NOTICE、执法检查建议书 ENFORCEMENT_ADVICE 三类的辅助生成整体下线**——`OUTPUT_TYPES` 删这三键、`generate_document` 只走 `_generate_from_template`,原 path-B(`create_task` 底座文书任务 + `_build_input_refs` + `_ensure_analysis_input` + `_persist_text_resource`)删成死代码后移除;分析确认(ASSEMBLED)时 `_assemble_advice` 不再自动生成执法检查建议书,只组装 `final_output`。工作台流程随之从九步收为八步(去掉告知书 inform 步);决定书前置由「已确认告知书」改为「已确认通知书」。**保留** `DOCUMENT_CATEGORIES` 里这三类,外部做好的仍可作为档案文件上传归类。文书生成提示词全局库同步只剩五类。门禁:`test_enforcement_flow`(八步投影)、`test_intake_flow`(本地成文 + 全局库五类)、`test_navigation`。 > **后续更新(2026-09-29 文书更名)**:上文的「行政处罚通知书」是开发时的误名,准确说法应为「**行政处罚告知书**」,已全项目更名(`PENALTY_NOTIFICATION` 的中文显示名 通知书→告知书,内部 key 不变)。同时把已下线的旧 `PENALTY_NOTICE`(原也叫"行政处罚告知书")**合并删除**:其在 `DOCUMENT_CATEGORIES` 的类型定义/别名删除,存量档案文件(3 份)迁移到 `PENALTY_NOTIFICATION`。`DOCUMENT_CATEGORIES` 从 24 类降为 23 类。旧文件名「行政处罚通知书」仍作别名兼容归到告知书。「检查通知书 INSPECTION_NOTICE」「责令整改通知书 RECTIFICATION_ORDER」不受影响,仍是各自独立文书。 **文书生成提示词内联到各生成页(2026-09-30)**:侧栏独立「文书生成提示词」全局页及其页面模板 `document_prompts.html`、页面/全局 API 路由已删除。提示词改为内联在各文书生成页下方(报告书 `archive_report.html`、其余四类共用 `archive_draft.html`):Markdown 编辑器 `#docPromptEditor`(**统一走 Vditor**,height=580,不可用回退 textarea)+ 「保存提示词」「恢复默认」「重新生成」,接 per-archive `GET/POST .../archives/{id}/document-prompts`。全局库表 `wg_document_prompt` 与读兜底 `_effective_document_prompt` 保留,历史全局覆盖仍生效。生成兜底顺序不变:请求 > 档案内已存 > 全局库 > 内置默认。**五类文书生成流程**:读模板占位符 + 已确认字段(`template_values`)+ 提示词交大模型组织**叙述型**占位符(白名单 `NARRATIVE_PLACEHOLDERS`),填回 Word 模板保留版式;硬事实(当事人/地址/法条/金额/文号)用已确认值模型改不动;模型不可用/降级 **fail-closed 回落本地填充**,不空稿不编造。执法检查报告书的默认提示词(2026-10-03)要求呈批表「案件情况」写成完整四句、法规名取已确认处罚依据,正文定性依据和处罚依据按「《法规名》第N条」接原文不截断;运行时生效的是全局库 `wg_document_prompt` 里的同口径覆盖,代码默认只是兜底。报告书页只读预览仍纯本地填充。 **政策检索(2026-10-10)**:页面复用确认法律依据的违法事实栏检索法规条款。检索词是违法行为;主体和业务领域选了才拼进去,没选(含自动勾选后再点一次取消)就不进入检索词,也不按空值重排。范围固定为法规条款库全量(`law_clause`),不套用定性依据对《外汇管理条例》处罚条款的排除,也不检索处罚依据。这次检索只返回结果,不写档案、政策或检索记录。自由文本检索接口仍保留,政策管理页继续使用。 **侧栏(2026-10-09)**:不再使用下拉分组。工作台、执法档案、参考案例、文书模板、政策管理、政策检索、汇智助手、技能市场、用户设置、机构设置、权限配置、底座状态都是一级菜单。「行政处罚wiki」不再出现在侧栏,只读页 `/admin/waiguan/policy/penalty-wiki` 和接口仍保留。文书模板保存五类智能生成文书的参考模板。工作台、执法、政策、智能服务、设置这几组之间用横线隔开。2026-10-09 只改显示名称,地址不变:辅助执法检查→执法档案、执法检查案例→参考案例、检查文书模板→文书模板、外管法律法规→政策管理、法律法规检索→政策检索。页头、面包屑和工作台入口与侧栏同名。2026-10-19 起政策管理页不再放「词条库」按钮,`/admin/waiguan/policy/wiki` 页面已删除,词条 CRUD/检索接口仍保留。 **设置菜单(2026-09-15 重构)**:用户设置、机构设置、权限配置、底座状态现为一级菜单—— | 页面 | 路由 | 能力 | |------|------|------| | 用户设置 | `/admin/waiguan/settings` | 用户列表管理:按机构(含下级)/角色/名称筛选;新增用户(独立弹窗,含角色多选;所属机构用**可搜索+可折叠的机构树选择器**按层级选,默认选中当前登录用户所属机构)、查看详情、重置密码(系统生成 12 位临时密码,仅显示一次)、修改角色(独立弹窗,实时预览权限并集)、删除(不能删自己/超管)。 | | 机构设置 | `/admin/waiguan/settings/directory` | 机构树管理:按 parent_id 树形显示,名称搜索过滤;新增顶级/下级、编辑、删除(有下级或有用户时拒删);点机构查看其下(含下级)已注册用户。 | | 权限配置 | `/admin/waiguan/settings/permissions` | 角色/用户/机构**三维权限配置**(`permission:manage` 门禁)。三个 Tab:①角色权限——勾选调整每个角色拥有的权限点,存库覆盖内置模板;②用户权限——按机构/角色/名称筛选用户,弹窗为其配置显式附加权限(角色/机构带来的权限只读展示);③机构权限——机构树选机构,配置该机构默认权限(自动并入其下用户)。权限点一律显示中文标签。 | 个人资料编辑移到顶栏用户头像下拉的"个人资料"弹窗(复用 `GET/PUT /api/settings/profile`)。 **修改密码修复(2026-09-15)**:顶栏"修改密码"走 FastapiAdmin 内置 `POST /admin/password`,但 `app/providers.py::LoginProvider.password` 的自定义覆盖**只调 `logout`,从不校验旧密码、也从不保存新密码** → 用户改密码"无法保存/不生效"。恢复正确流程:校验旧密码(`check_password`)→ 两次新密码一致 → 新密码≥8位 → `admin.password = 明文; save(update_fields=["password"])`(Admin `pre_save` signal 自动哈希)→ 登出要求用新密码重登。错误分支模板用 `providers/login/password.html`(模板搜索根下真实存在的路径;父类原写 `password.html` 在本项目模板根不存在会崩,故不沿用)。真机验证:旧密码错/两次不一致均拒、正确则新密码可登录且旧密码 401 失效。 权限目录接口位于 `/admin/waiguan/api/settings/organizations` 和 `/admin/waiguan/api/settings/users`(含 `GET {id}`、`DELETE {id}`、`POST {id}/reset-password`、`GET organizations/{id}/users`、`GET roles`),由超级管理员维护机构树和用户注册信息。机构范围遵循本机构及下级机构可见、兄弟机构隔离、未归属默认拒绝的规则。 **权限三维可配(2026-09-15)**:此前权限只有两源且能力不齐——角色→权限硬编码在 `profile.py::ROLE_PERMISSIONS`(只能看不能改)、用户显式权限字段 `permissions_json` 存在但无端点写它(`api_create_user` 恒写 `[]`)、机构级权限概念根本不存在。本轮把统一权限模型的唯一计算点 `effective_permissions(roles, explicit)` 扩成三源,为三源各补持久化 + 配置入口: > 生效权限 = 角色模板权限(可配)∪ 用户显式权限(可配)∪ 机构默认权限(可配) (稳定去重并集) - **数据模型**:新增两张本地 admin 库表(`generate_schemas=True` 自动建)——`wg_role_permission`(角色覆盖表,只存改过的角色,查不到回退内置 `ROLE_PERMISSIONS`)、`wg_org_permission`(机构默认权限,`organization_id` 裸整数无外键,与跨库约定一致)。 - **计算改造**(`app/services/profile.py`):`effective_permissions` 新增可选参 `role_map`/`org_perms`(**向后兼容**,不传则回退内置模板,现有同步调用与测试零改动);新增 `async def resolve_role_map()`(内置叠加库内覆盖)/ `resolve_org_perms(org_id)` / `resolve_all_org_perms()`;`permission_allows` 同步支持注入。**鉴权路径**(`authz.py::has_permission`)改为注入库内覆盖 + 该用户机构默认权限——让"配置角色/机构权限"真正参与鉴权,不只是展示。 - **端点**(`/api/settings/permissions/*`,全 `require_perm("permission:manage")`):`GET catalog`(全量权限点+中文标签,此前无端点暴露扁平标签表)、`GET/PUT roles/{role}`、`GET/PUT users/{admin_id}`(补上从不写 `permissions_json` 的缺口)、`GET organizations` + `PUT organizations/{organization_id}`。写入统一过 `PERMISSION_LABELS` 白名单(越白名单 400 `INVALID_PERMISSION`)+ **防提权**(`_ensure_can_grant_permissions`,非超管不能授出自身没有的权限点,403 `WAIGUAN.FORBIDDEN`)+ 机构范围隔离(越权 404 不泄露)。 - **前端**(`templates/waiguan/permissions.html`):三 Tab(角色/用户/机构),复用 `WG.perms()/WG.isSuper()` 同口径裁剪——非超管勾不到超出自身权限的权限点(锁定禁用,已选则保留只读并回写避免误删)。回归测试 `tests/test_permission_config.py`(向后兼容、注入并集、覆盖生效、白名单过滤)。真机验证:三维读写 round-trip、越白名单 400、不存在角色 404,且改角色后用户 `role_permissions` 随之变化(覆盖真正入鉴权)。 底座下游已拉起并端到端打通(2026-09-13): 1. M05 知识服务生产形态上线:6 套依赖全容器化(Docling / Qdrant / M09 审计 = 真镜像;TEI 兜底 stub;**OpenSearch 2.17.0 + IK 中文分词** = 真镜像;MySQL `m05_knowledge`)。知识化、检索端到端通。 2. M01→M04 / M01→M05 服务间签名信任已配好(含 `@authority` 组件匹配、nginx 前端端口透传等坑已解)。 3. 检索改用真 OpenSearch + IK:中文按词切(`经常项目结售汇` → `经常/项目/结售汇`),非 `standard` 逐字。 历史上"仅 M01+M04 在跑""M05 无法本地跑"的说法已被本次上线推翻,下方相关段落保留作演进记录并已标注。 ## 上层业务编排层(已实现) 政策库 / 检查档案 / 文书生成等业务原本在 Spring `yudao-module-waiguan`,M01 网关只给原语。 本前端在 M01 之上补了一层 **Python 业务编排**,忠实对齐 Spring 契约(状态机 + 乐观锁 + 门禁 + 降级不伪造)。 - **业务库**:独立 `app/business.sqlite3`(`config.BIZ_DB_URL`),与登录库分离;`main.py` 用 register_tortoise 双连接。 底座扮原语角色:文件字节 → M04 资源、AI 识别/生成 → task、知识化 → M05。 - **状态枚举(投影真相源)**:`app/domain/*_state.py` 只保留枚举、批次派生、权限/只读门禁;**推进边不在这些文件手写**。 - 政策录入 `intake_state.py`:对齐 Spring `PolicyIntakeStatus` 10 态 + batch 派生。推进/失败/重试/取消由 `app/workflows/policy_intake.bpmn` 执行。 `READY_FOR_REVIEW`/`NEEDS_COMPLETION`/`EXTRACTION_FAILED` 允许回 `EXTRACTING`(用户点"重试"重新识别)—— 历史 bug:READY→EXTRACTING 边缺失导致已识别成功的项点重试报"非法状态流转",已加边 + 回归测试 `test_retry_from_ready`。 **已在 EXTRACTING 再点重试不走自环**(定义无 EXTRACTING→EXTRACTING):换新任务并清空错误码;`refresh` 对失败/未知/传输错误回写 `EXTRACTION_FAILED`。回归 `test_retry_from_extracting` / `test_refresh_failed_task_leaves_extracting`。 - 政策库 `policy_state.py`:draft → 确认元数据 → 知识化 → 发布 → 失效,边在 `app/workflows/policy_lifecycle.bpmn`。 - 检查档案 `archive_state.py`:状态枚举 + 只读门禁 + 文书生成门禁 + **推进按当前态动态判权**(`perm_for_advance`:DRAFT 送审 archive:write,其余环节 archive:approve)+ **分类确认门禁**(`is_generation_input_allowed`:未入正式档案/未确认/未分类不得作为生成依据)+ **建议稿/正式稿双版本**(`edition_label`)+ **按正文识别文件类型**(`classify_archive_document` / `guess_document_category`,抽不出正文才回退文件名)。**推进/退回边不在本文件手写**,由外部流程定义执行(`app/workflows/archive_approval.bpmn`,适配 `archive_workflow.start_instance` / `advance_instance` / `reject_instance`),结果投影到档案行 `status` + `workflow_state`。 - **编排服务**:`app/domain/{intake,policy,archive,archive_analysis,wiki}_service.py`,所有写操作过状态机/流程定义 + 乐观锁(version)。词条发布走 M04 原文 + M01 wiki 导入,不另开流程定义。 - **业务模型**:`app/store/models.py`(IntakeBatch/IntakeItem/CandidateField/Policy/InspectionArchive/ArchiveDocument/ArchiveTransition/ArchiveFactAnalysis/WikiPage)。 - **路由 + 页面**:`app/routers/{policy,archive}.py` + `templates/waiguan/policy_*.html`、`archive*.html`。 | 业务域 | 页面 | 关键动作 | |---|---|---| | 政策管理 | `/admin/waiguan/policy/library` | 知识库语义检索 + 列表(状态/关键词筛选) + **添加政策**(上传原文即建 draft 政策并跳详情页,见下)。检索走 `POST /admin/waiguan/api/knowledge/search`,结果展示命中正文(网关字段 `excerpt`),标题按文稿资源号链到政策详情,对不上本库时用引用号打开原文。列表条目**名称与状态徽章各占一行**,展示**当前节点**(八步投影) + 状态 + **编入哪些库**徽章。**2026-09-23 列表瘦身**:条目操作只留「查看详情 / 刷新知识化」;阅读文稿、确认元数据、知识化、发布、失效、删除全部收敛到详情页,列表不再内联渲染文稿 | | 政策详情 | `/admin/waiguan/policy/detail/{id}` | **政策全生命周期唯一操作台**。**顶部八步流程图**(上传→识别转换文稿→确认修正文稿→识别→复核→确认→知识化→发布)→政策文稿卡(`#manuscriptCard`,在政策要素之前;**上传后**异步转 markdown;只读走 `WG.renderMarkdownInto`(markdown-it)且**只渲染不含 YAML 头的正文**;下载仍是完整文稿;读时按现行键重写 YAML 头,历史「适用对象」「内容摘要」不再进入文件信息;单位负责人用 **Vditor** 编辑器改正文,草稿也可改;**确认文稿**后才启动识别,保存不自动确认/知识化)→政策要素卡(`#lifecycleCard`;draft 态显示识别横幅:转换中/待确认文稿/识别中(轮询回填要素)/就绪待复核/失败或识别中均可**重新识别**;含日期;**下载原文** + **编辑基本信息**内联表单:日期文本框可手输 `YYYY-MM-DD` + 日历按钮(`.wg-date` + `showPicker`,`isValidYmd` 校验真实日历日)、失效日期空值默认 `2099-12-31`、效力状态下拉(有效默认/失效,落库 `effect_status`,不联动生命周期)、必填项红星+空值红框(`.wg-required`/`.wg-missing`,口径同 `REQUIRED_FIELDS`);**确认基本信息**(draft→metadata_confirmed,服务端校验必填,重写 YAML 头);适用对象不作政策级展示;**重新识别 / 编辑基本信息 / 确认基本信息**在政策要素卡 `#elementActions`(转换/确认文稿期间不显示编辑/确认;失败/就绪/识别中可重新识别);页头在政策名称下:主操作一排(下载/**发布政策**/删除政策)+ 知识库复选框与「提交知识库 / 取消知识库」同一按钮单独一排(已入库另增「重新提交知识库」;已发布也可直接删,不展示「失效」))→条款标注卡(内容摘要由智能体在切片时一并生成,标注建议可补;人工确认后才进入检索标题)→知识化卡(`#knowledgeCard`;**多选库(法规条款库/裁量规则库)**复选提交,须文稿就绪;各库状态表;**按目标撤回**:软下架检索副本、重置子状态,生命周期不动;知识图谱暂未开通(网关白名单不接受 extract_graph))→重复检测面板 | | 词条接口(无独立页面) | `/admin/waiguan/policy/api/wiki*` | 2026-10-19 起政策管理页「词条库」按钮和 `/policy/wiki` 页面已删除。术语词条 CRUD / 发布 / 下架 / 检索接口仍在:发布写入原文并导入独立词条集合,同一词条再发布替换检索副本,下架/删除源级停止命中。写操作 `knowledge:write`(部门负责人及以上);检索 `knowledge:search` | | 行政处罚wiki | `/admin/waiguan/policy/penalty-wiki` | 不进侧栏。**只读展示**由参考案例萃取、人工核对发布的行政处罚 LLM wiki 词条:按违法类型(category)分组卡片、总词条排前、明细折叠、markdown-it 渲染正文、顶部检索、左侧类型 TOC 导航。只读 API `GET /api/penalty-wiki`(`keyword` 检索,`status=published` 分组)。LLM wiki 底座=M04(正文)+M05(检索副本)+M03(起草)三层 | | 执法档案 | `/admin/waiguan/archive/list` | 新建填写被检查对象、对象类型(金融机构/非金融企业/个人,**默认金融机构**)和检查人(**可搜索机构树选择器按层级选,默认当前用户所属机构**;提交机构全称,留空则服务端回填当前单位)→上传文档→线性推进/复核退回→封存。**列表标题按「年份-被检查对象-检查人」组合展示**(如 `2026-锦城银行南海支行-四川省分局`,后端 `display_title` 计算、缺项优雅降级回退 `title`),元信息含**录入时间**。**支持状态/年份/检查人筛选 + 全文搜索**(标题/被检查对象/检查人/摘要/类型,不区分大小写);**年份/检查人下拉选项仅来自机构可见范围**(与档案本身的机构隔离同口径,不泄露他机构信息——区别于案例库不隔离) | | 档案详情 | `/admin/waiguan/archive/detail/{id}` | 页顶**行政执法检查八步流程图**(已完成/进行中/未开始/已跳过分色;**不显示档案概况卡、不显示页头「推进」按钮、不显示「进入辅助分析」、不显示「回退并重新分析」**)→**复核退回**(REVIEWING→DRAFT,须填理由)→封存/纳入案例库(生命周期状态不限,2026-09-30 解除原“仅办结或已归档”限制;只要挑得出一份已入档已确认权威文书即可经 M01 `POST /api/v1/case-knowledge/imports` 导入独立案例集合,`source_id=ksrc_case_<档案id>`;移出/删档案走源 inactive,源不存在幂等)→文书生成(`#genCard`,网格先放「行政文书类型识别」「违法事实辅助分析」,其后生成按钮**按已有正式文书启用,不必按顺序**)→**添加文件**(`#docsCard`,一个按钮,选一个或多个后立即串行上传+按份报告成败)→**删除档案**(含文档 + M04 资源回收;封存中/已归档不可删,与后端门禁一致,此时不显示入口)。违法事实辅助分析已迁独立页,详情不再嵌分析卡。**文档管理:文件类型按正文识别**(24 项下拉——未分类 + 22 业务类型 + 其他材料;上传时抽 PDF/DOCX/文本正文匹配文种词,抽不出字才回退文件名;**猜测未确认**,须人工确认或改类型后才可作为文书生成依据;不依赖底座分类工作流)、按类型分组展示、**点文件名默认打开在线预览页** `/detail/{id}/documents/{doc_id}`(PDF iframe / 图片 / **Word 与文本走 `GET .../preview` 按段落/表格出消毒 HTML**;xlsx/pptx 仍下载兜底)、**重新识别类型**(读回 M04 字节再跑 `reclassify_document`,保持未确认;**不是**文档「退回」)、人工指定类型(改文件名+改类型即确认)、替换、删除、确认类型(上传件一并过复核);AI 建议稿仍「确认正式稿」/「退回」/「保存到正式档案」**(写操作均需 archive:write + 过只读门禁;预览/下载所有可见档案的人均可用;正式稿不可替换/重新识别,可编辑档案允许删除正式稿以回退八步投影)。页面底部为**流转记录**时间线(审批留痕:谁/何时/意见) | | 违法事实辅助分析 | `/admin/waiguan/archive/detail/{id}/analysis` | 以下交互细节为早期页面记录;当前底座接入以 [知识 Skill](docs/skills/m01-knowledge-stateless.md) 和 [分析场景](docs/scenarios/archive-fact-analysis.md) 为准,旧 M05 分析端点与 60 秒超时不再适用。独立页,不进侧栏。页顶七步:分析事实确认书 → 确认证据与案情 → 确认法律依据 → 确认处罚意见 → 分析结果 → 已确认分析结果。**从全部已确认事实确认书综合后按违规类型分类,每类一张卡片**,逐项列出:①事实摘要(当事人在何时办理什么业务时存在什么违法行为,违反了什么规定,共涉及多少金额或获利多少;不写发现经过和详细过程,不编造材料中没有的内容)②一句话定性 `qualitative_summary` ③违反的法律法规(引用原文+《法规名》+条款)。拆分+摘要+定性由 M03 `_think_facts`(deer-flow)生成、人工确认后可改,模型不可用退化一条并本地关键词兜底;引用法规逐条走 M01 `case-analyses`(fail-closed 不编造)。BPMN 五步与进入硬门不变,多条在步骤内循环(`_iter_fact_slices` 按 `fact_id` 对回每条)。**页顶「事实确认书原文」卡**把每份已确认认定书经 M08 转 markdown,多份按卡片切换只读展示(Vditor),转换异步轮询;转换失败给「重试转换」按钮(`POST .../fact-findings/{doc_id}/retry` 只重命中那份);旧版二进制 `.doc`/WPS 由 M08 转换器 soffice 预转,仍失败回退本地纯文本;表格限制在卡片宽度内,单元格自动换行。「开始分析」点一次即锁定。证据整案共享,从整段「证据」标题之后取到签字栏、备注之前,没有该标题则留空。确认证据与案情不再检索;该步页面底端展示本步发给大模型的识别提示词,可修改,点「保存并重新识别」(`POST .../analysis/rethink`)按新提示词重跑事实提取、只重写事实卡片不推进步骤,提示词留空回退默认。确认法律依据不再显示「已确认内容」,每类改为上下瀑布:标签在上、内容在下,依次是违法事实摘要、违法主体类型、违法业务领域、检索定性依据、检索处罚依据;每条带事实编号,条目底部「检索依据」只提交被点的那一类,后端只按事实编号配对;空白主体和领域只按本条原文给页面预选,不写入检索字段,已选不覆盖;主体或领域一改就写入这一类并清掉这一类已选条款;每类各自检索、各自确认,条数可变。一点检索就在本类定性依据和处罚依据处显示「正在检索本类依据…」,返回后只替换这两处,旧勾选和旧确认条款不再沿用,其它类别不重画;定性和处罚、以及勾选的多个业务领域排队检索,同一时刻只向底座发一路,超时或失败会关掉这条连接;法律依据检索每路最多等 30 秒;有一路返回就保存这一路,失败原因只记在被点的这一类,不写到整份档案上,也不挡住其它类已经返回的依据;开始这一次检索时清掉上一次留在整份档案上的读超时;调 M01 `case-analyses`(定性库、处罚库,规则排序;不打开大模型重排,因为网关等知识服务只有 60 秒),摘要为空,或违法主体类型、违法业务领域没选,则拒绝,不推进步骤。确认法律依据只收下依据,不调裁量。确认处罚意见不显示「已确认内容」。违法事实卡片之前按《外汇管理行政罚款裁量办法》原文列出第八条至第十三条的全部判断项,供对照,不需勾选。每类只确认违法金额或违法所得、以及七类量罚标准里的一项。金额从已识别字段带出,页面上已有编辑框,确认时直接采用,不再另设处罚金额输入框。不再勾选《外汇管理行政罚款裁量办法》第二章分项,也不再点「分析」调用 `discretion-analyses`。七类是:不予行政处罚、从轻或者减轻行政处罚、按较轻情节对应的处罚幅度进行处罚、按一般情节量罚区间下限到中间值(不含)之间进行处罚、按一般情节量罚区间中间值到上限(不含)之间进行处罚、按较重情节对应的处罚幅度进行处罚、按严重情节对应的处罚幅度进行处罚。页面保存这七种全称,手选标准不会被裁量结果覆盖。未选择标准不能确认。进入下一步时,直接把已确认的违法所得或违法金额作为处罚金额,不再调用 `penalty-analyses`,也不再提示「请确认处罚金额」。一般情节两个半段各选主附表自己的列,建议金额取该列中值,外管不再按返回区间折一次。确认处罚意见后进入分析结果,每类一张上下瀑布卡片,依次展示性质、概述、详细描述、定性依据、处罚依据、处罚建议、量罚档位,不用表格。可退回前面任意步骤(`POST .../analysis/return`),退回后该步及后续步骤都要重新确认。最后点「确认分析结果」进入已确认(ASSEMBLED),**只组装结论、不再自动生成任何文书**(建议书辅助生成已下线),随后返回档案详情页,五类文书在文书生成卡片按需生成。不接 `case-advice`。 | | 档案文档预览 | `/admin/waiguan/archive/detail/{id}/documents/{doc_id}` | 在线预览 + 类型下拉(人工指定即确认)+ **重新识别类型** / **替换** / **确认类型**;Word/文本 iframe `GET /api/documents/{id}/preview`(docx 按段落/表格消毒 HTML,不把 HTML 写入检索摘要);返回档案详情 | | 文书生成 | 档案详情页内 | 网格按钮生成 **AI 建议稿**:执法检查报告书 / 行政处罚立案审批书 / 行政处罚建议书 / 行政处罚通知书 / 行政处罚决定书。**报告书按钮直接打开独立页**,分析未确认时按空内容打开。版式对齐已上传的正式检查报告。地址、负责人和案件情况取已入档立案审批表;是否建议处罚只按已确认档位、金额或建议来写;检查时间和证据条目取已入档事实确认书原文;只有证据、没有检查经过时写「我分局对当事人的违法事实进行了调查,取得了以下证据:」;没有的留空,呈批表后分页。页上再点「生成执法检查报告书」才落档,不覆盖已入档文书。提示词不在档案详情,而在对应文书页(报告书在报告页,立案审批书、建议书、通知书、决定书各自一页),保存后按该页提示词生成;非报告书作为材料交给原有文书生成,不替换已发布 skill。五类按 Word 模板生成 docx 建议稿,生成后到文档预览页查看和下载,正式稿不能覆盖。**检查通知书 / 行政处罚告知书 / 执法检查建议书的辅助生成已下线**(`OUTPUT_TYPES` 只剩这五类,原 create_task 生成经路已删;工作台流程也随之从九步收为八步,无告知书 inform 步);外部做好的这三类仍可作为档案文件上传归类。**第 4–7 步按已有正式文书打勾,按钮按条件启用、可同时点**(已有正式文书只打勾,不关掉再生成建议稿;条件未齐或已跳过禁用并给中文原因)。**启用条件**:报告书按钮始终可进,落档仍须辅助分析已确认;立案审批书和建议书须已确认报告书;处罚告知书须已确认处罚建议书或执法检查报告书;决定书须已确认处罚告知书。**行政处罚建议书默认模板为真实占位符模板(违法事实/定性依据/处罚依据/处理建议段),非只有标题的空壳**。处罚金额取人工「拟罚款金额」框(不再把违规额原值当罚款),违规金额带币种/外币折人民币;确认证据与案情步可填「调查开展情况」写进报告书。若无违法、违法轻微、检查行为不合法或审理后免罚,可直接办结归档。生成件默认 `filed=false`,只出现在本卡片;「确认正式稿」仍在工作区内复制正式稿,**不自动入档**;须先确认正式稿再点「保存到正式档案」才进入档案文档并可作为后续生成依据,建议稿直接入档会被拒绝。仅用已入档且已确认类型的材料作依据;重新生成不会覆盖正式稿 | | 参考案例 | `/admin/waiguan/cases` | **默认列出最新 20 个案例**(纳入案例库 `case_library=True` 的档案,按录入时间倒序)→**按年份、检查人下拉筛选**(选项由全量案例派生、不随筛选消失)→**全文搜索**(标题/被检查对象/对象类型/检查人/摘要 + 档案文档的文件名/类型中文名/正文预览,不区分大小写)→点进档案详情。数据源本地库 `GET /waiguan/archive/api/cases`,列表不走语义检索。**纳入案例库**走 M01 `POST /api/v1/case-knowledge/imports`(独立案例集合,不是政策 `/knowledge/imports`,也不是历史单数 `/knowledge/import`);生命周期状态不限(2026-09-30 解除原“仅办结/已归档”),须有已入档已确认权威文书。移出走源 inactive。**案例不做机构范围隔离**(纳入案例库=有意发布的跨机构共享学习材料,与政策知识库同性质) | **测试**:`tests/test_intake_flow.py`(内存库 + 桩底座)覆盖 6 个场景——录入 happy path、乐观锁、上传降级、 政策生命周期、档案生命周期、文书生成。运行 `.venv/bin/python -m tests.test_intake_flow`。 **录入列表持久化**:录入项列表原先只存前端内存,刷新即丢。已加 `GET /api/intake/items` (`intake_service.pending_items`:按 actor 活跃批次归集,排除终态 `POLICY_DRAFT_CREATED`/`CANCELLED`), 录入页加载时拉取恢复列表。路由须放在 `/api/intake/items/{item_id}` 之前,否则被动态段吞掉。 **要素录入框按类型 + 依据可读化**:录入项详情页识别要素按字段类型分派控件——日期字段 (`published_at`/`effective_at`/`expired_at`)用 ``;枚举字段用 `