Commit Graph
23 Commits
Author SHA1 Message Date
张胜宇 e5b4d02b0d merge: 集成投顾组 3 个提交(解除与「投顾模块清除」的冲突)+ 客服 Agent 重构收口
## 为什么要合并
远端 `origin/qyqy_develop` 领先 3 个提交(`5607751` / `2fe7d0c` / `74b7d00`:投顾需求与架构文档、
客户主动申报投顾方案 + 受理自动出草稿、方案交付落点与推荐依据 LLM 增强),而本地 `5d0becb`
按 `D4.4` / `D4.5`(CS-PURGE-2026-012/013)把投顾模块整体清除了。**两个目标不可兼得**:
远端新代码反向 import 已被清除的模块(`app.model.investment_goal`、
`app.service.product_recommendation_service`、`app.service.advisor_rollout_service`),
强行推进只会让两边都跑不起来。

**裁定:投顾组的新功能 > 本地的投顾清除。** 依据是 `D4.4` §0-②③ 自己写下的风险
——按名字清投顾会同时拆掉产品数据底座与 MVP 硬阻断,并失去"改 6 个底座文件时的对照组"。
本次合并因此**恢复投顾模块**;就代码面而言,`D4.4` / `D4.5` 的清除结果被本次合并取代
(留痕见 `开发文档\D1.6` §4.37)。

## 冲突怎么解的(12 处)
- **8 处 modify/delete 取远端**:`recommendations.py` / `product_recommendation_service.py` /
  `employee-advisor/dashboard/{actions-module.js,dashboard.css,dashboard.js,index.html}` /
  `tools/{check_portal_modules.py,grant_advisor_role.py}` —— 即"我删、远端改",保留投顾文件。
- **3 处内容冲突取远端**:`app/main.py`(投顾 import 与 `include_router`)、
  `common/api-client.js`(投顾端点表)、`tests/unit/api/test_portal_frontend.py`(4 条投顾前端契约)。
- **1 处取远端 + 保留我方**:`app/main.py` 解除冲突的同时,保留本轮的
  `/customer-service-test` 挂载移除(该联调页与用例已随重构作废)。

## 因"取消清除"而必须回滚的语义改动(否则恢复出来的投顾代码跑不动)
- `app/service/agent/bootstrap.py`:恢复 `AdvisorAgent` 与 5 个投顾工具注册
  (`query_investment_goal` / `analyze_portfolio` / `generate_asset_allocation` /
  `recommend_products` / `compare_products`);客服 Agent 注释按本轮口径保留。
- `app/core/config.py`:恢复 `advisor_rollout_enabled` / `advisor_rollout_customer_ids`。
- `app/static/portal/common/layout/app-shell.js`:恢复投顾工作台导航与 `advisor` 角色名。
- `tools/seed_test_rbac.py`:恢复"admin 取全量元组"的授权模型(保留远端新增的
  9070-9074 权限码与客户侧 9071/9072 绑定)。
- `tools/portal_api_check.py`:恢复投顾实测用例(AD003/AD005/AD011/A047 与 `advisor_t` 登录),
  并**新增判定**:被渲染的集合为空(0 条)时判 `SKIP` 而不是 `FAIL`
  —— "没有行"与"字段没带"是两回事,混报会把排查方向带偏。
- `app/static/portal/common/api-client.js`:以远端为基准,重新叠加本轮的
  **访客令牌 `Authorization` 优先**修复(浮窗访客身份稳定性)。

## 数据库夹具同步(代码恢复 ⇒ 夹具也要恢复)
- `tools/grant_advisor_role.py`:新建 `advisor` 角色并授权(实测 34 项权限)。
- `tools/create_test_user.py --id 9020 --username advisor_t --role advisor`:重建演示账号。

## 集成期发现并修掉的过期断言
- `tests/unit/test_advisor_migration_contract.py`:alembic 末端钉死值仍是
  `20260914_baseline_auto_increment`,而远端新增了 `20260916_advisor_service_request`
  ⇒ 这条断言**在远端分支上本身就是红的**。本次把它更新到新末端并补了注释。

## 验证(本机实测)
| 门禁 | 结果 |
|---|---|
| `pytest -q`(全量,含集成) | **1909 passed / 3 skipped / 0 failed** |
| `ruff check app tools tests` | 20(远端分支 22,本地仅客服线基线 19) |
| `mypy app` | 2(= 既有基线) |
| `tools/portal_api_check.py` | 40 项:通过 35 / 失败 0 / 跳过 5 |
| `tools/e2e_smoke_test.py --read-only` | 31/31 |
| `_eval_harness/http_probe.py` | 11/11 succeeded |
| `_consistency.py` | GATE PASS |
| `_fe_boundary_http.py`(前端入参边界真机) | 全部符合预期 |

## 未做(如实登记)
- **投顾演示数据未灌**:`AD011` / `A047` 需要 `advisor_product_suitability_reference`
  这类带 `source_url` + `document_sha256` 的证据行,而披露文件不在仓库里;
  `tools/seed_advisor_demo.py` 明确"不编证据"(fail closed),故这两条按空集 SKIP。
- **客服线文档目录仍未入库**:`客服agent/`、`开发文档/`(权威副本在本机)与
  `_chunks_report.txt`(本地构建产物)依旧排除在提交之外。
2026-09-20 14:52:35 +08:00
张胜宇 5d0becb67d 客服 Agent 重构收口:五出口决策链 + 知识库档位隔离 + 前端入参边界(答辩演示版本)
一、客服 Agent 智能增强(正面回应"不智能、动不动就转人工")
- 决策链由 2 个出口扩到 5 个:E1 澄清 / E2 计算型 / E3 知识直返 / E4 证据约束生成 / E5 分级回退
- 转人工从"默认动作"降为最后一档 E5c,只保留 4 类白名单:
  P0 反诈 / P1 账户与个人数据 / P2 写操作与争议 / 用户明确要求人工
- 46 条金标实测(修复前 → 修复后):
  转人工率 43.5% → 10.9%;出口准确率 45.7% → 100%;事实正确率 69.6% → 100%
  禁忌违反 1 → 0;档位越权 / 无出处数字 / 误拒 四项零容忍全 0
- 安全不变量 INV-1~INV-5;零容忍规则未删,改的是挂载点
  (输出侧字面黑名单 → 检索层档位隔离 + 判定层合规词表 + 输出守护)

二、知识库:档位单点化与物理隔离
- 新增 app/core/knowledge_tier.py 作为档位规则唯一落点(G-03),
  knowledge_contracts.py 原定义块改为显式再导出(X as X,非副本)
- 档位过滤由 bool 默认值(fail-open)改为 tiers 必填集合(缺参即 TypeError)
- Milvus 侧四集合按 visibility 分区键物理隔离;双 schema 收敛为一套
- 新增 app/core/actor.py:访客三元组与匿名判定的唯一构造/判定点(G-01/G-01b)
- 新增 app/core/fund_fee_rules.py:费率计算纯函数

三、前端入参边界对齐(本轮 W11 新修,4 处"校验宽于存储")
- message 加 max_length=8000(与浮窗 widget.js 的 maxlength 一致)
- session_id 加 1—64;idempotency_key 上限 128 → 64(对齐列宽 String(64))
- feedback_type 加 max_length=32(对齐列宽 String(32))
- 8 条路径参数补 min_length=1 + max_length=64 + 字符集正则
  ({session_id} / {run_id} / {handover_id})
- 改前超限值会落到 MySQL 才失败(500);改后一律 422 AGENT_INPUT_INVALID + 字段级定位
- 新增 tests/unit/api/test_frontend_boundaries.py(33 例),含"端点表 ↔ OpenAPI 全量对照"

四、投顾模块整体清除(D4.4 / D4.5)
- 删除投顾相关 controller / schema / model / repository / service 及门户页面
- tools/portal_api_check.py 同步作废 AD003/AD005/AD011/A047 四条用例与 advisor_t 登录
  (端点与账号均已不存在,此前稳定报 3 条假红)

五、验证(提交前实测)
- pytest -q:1856 passed / 2 skipped / 0 failed
- ruff check app tools tests:19(= 基线);mypy app:2(= 基线)
- 前端接口契约体检 portal_api_check.py:38 项,通过 34,失败 0,跳过 4
- 全链路冒烟 e2e_smoke_test.py --read-only:31/31
- HTTP 全链路探针 http_probe.py:11/11 succeeded
- 跨文档一致性 _consistency.py:GATE PASS
- 真机边界复验 12 条:12/12 符合预期

六、纪律与文档
- 可改文件白名单 A-09(docs/46)与底座会签申请单 A-10(docs/47,组 1—组 4 全部受理)
- 零 DDL:未新增/修改任何表结构,89 张业务表与基线一致
- 证据留痕:docs/evidence/**(含 46 条金标 score、快照、清除与重建记录)
- 未提交(刻意排除,见提交说明):仓库内 客服agent/ 与 开发文档/ 是 2026-09-16 前的
  过期副本(Todolist 440 行 vs 权威 D2.1 1167 行),权威正本在仓库外;
  _chunks_report.txt 是 tools/build_knowledge_chunks.py 生成的本地产物
2026-09-20 14:33:30 +08:00
zhangshy 2a3146e050 增加风控列表总数并优化站内提醒 2026-09-13 23:51:45 +08:00
张胜宇 e38ece32bf 前端提交 2026-09-13 15:56:54 +08:00
lzf_0626 cb13f9cf45 fix(types): trade_service._next_id 的 model 参数标注改为 Any(mypy: type 无 id 属性) 2026-09-12 16:30:54 +08:00
wangjianlong_0626 8a0cbab636 fix(memory-projection): 订正 outbox 取值口径并接通画像投影链路
背景:memory_sync_outbox 这条链此前**完全没有消费者**,且生产端照 docs/00 §6.4.6
写成大写 MILVUS/NEO4J + 中文「待处理」,而消费端按 target_store 的**值**分派 handler、
且只领 status in {pending, failed} —— 两个条件都不满足,事件任何消费者都领不到、
永久滞留且不报错(唯一键 (event_uuid, target_store) 对大小写无约束,MySQL 也不报错)。
根因是代码与测试都硬编码字面量,所以测试跟着一起错、谁也没拦住。

订正
- profile_generation_service:取值改为全仓一致的小写(milvus/neo4j/upsert/pending)
- 测试改为引用常量并断言消费端契约,不再硬编码(硬编码是本次跑偏的直接原因)
- 新增契约回归测试:断言大写值分派不到 handler、会进死信,谁改回大写立刻红
- 新增 tools/normalize_memory_sync_outbox.py:订正历史脏行(默认 dry-run、幂等)

接通投影链路(此前零消费者)
- 新增 Milvus 集合 user_long_term_memory_v1 及建集合工具(幂等、不覆盖已有集合)
- 新增 MilvusProfileProjection / MilvusProfileVectorClient,并修掉移植带来的两处必炸点:
  customer_id 由「必须 int」放宽为接受数字字符串(本仓所有生产者都写 str,
  不放宽则每个事件必然失败);不可投影的 memory_key 由「整批 raise」改为跳过留痕
  (否则一条 constraint: 记忆毒死该客户整批,而受控词表 13 个键里有 7 个不满足前缀)
- 新增 MemorySyncOutboxWorker(领取/指数退避/死信骨架保留原样)并接入 WorkerRuntime
- milvus → 向量投影;neo4j → 复用主干 ProfileGraphProjectionService(方案 A,
  不引入第二套投影,避免同一事实在图中两种说法、违反主干既有的只投影已确认事实的不变式)
- 生产端从 memory_unit(status=active) 组装 memory_sources,随事件带上确定快照
- 前置移植 conversation_privacy:写外部存储前脱敏手机号/证件号/银行卡等

验证
- 新增 17 个单测;全量 2 failed, 1307 passed, 2 skipped
  (2 个失败为既有环境项:断言请求体中文原文而 httpx 序列化成 \uXXXX,非本次引入)
- mypy app → 0 错(227 文件);audit_schema → 89 张业务表无缺失/意外,未改动表结构
- 真机:真实 embedding(1024 维) + 真实 Milvus 写入并回读通过
- 整合链路(测试记忆 → 生产端组装 → outbox → 消费端投递 → Milvus 回读)通过,
  且 MySQL 已回滚、Milvus 无残留

文档
- 新增 docs/32-记忆投影链路实现说明.md:真实口径、根因、契约与验证证据(供接手)
- AGENTS.md:新增该易错点;新增 Windows 中文输出乱码的正确命令(-X utf8);
  校正测试基线与 mypy 文件数

未做:未改 docs/00 基线、未动数据库迁移、未改投顾线代码、未启动常驻 Worker。
遗留:投顾线两处生产者的 payload 缺 memory_sources,会被消费至死信,待架构师确认是否投影。
2026-09-12 10:45:40 +08:00
Windows bbf623a464 merge: integrate advisor capabilities on latest qyqy base 2026-09-11 21:03:58 +08:00
qyqy cbd6de2721 Merge remote-tracking branch 'origin/qyqy_develop' into nl-merge-latest
# Conflicts:
#	app/service/model_gateway.py
2026-09-11 15:38:11 +08:00
wangjianlong_0626 e4c4099aaa wip: 客服Agent + RAG + 画像收尾(基于 6516ccb) 2026-09-11 14:46:40 +08:00
Windows ff71a1a724 feat: add dynamic advisor asset allocation 2026-09-11 14:44:29 +08:00
lzf_0626 661b17c244 风控全量查询封顶并在响应里标注截断(docs/25 P3 #20)
预警详情里的资金流水/持仓/登录记录、以及日报的四组预警都是"一次性取全量"。某个客户
的历史数据一旦异常膨胀,单次响应就能把内存和连接拖垮;登录记录此前更是连时间窗都
没有。

处理原则是"封顶但**不静默**":详情证据每类最多 200 条、日报每组最多 5000 条,用
`limit + 1` 多取一行判断是否真被截断(只看"取满没取满"会把恰好等于上限的正常数据
误报成截断),被截断时:

- 详情新增 `evidence_truncated: string[]`,列出被截断的证据类型;
- 日报新增 `data_truncated: bool` —— 日报的 daily_alert_count 等数字直接来自行数,
  静默截断等于给出一份看起来正常、实际少统计的日报,那比报错更难发现。

`scalars(...)` 统一用 `list(...)` 而不是 `.all()`,实现不再依赖 ScalarResult 的专有方法。

新增 tests/unit/repository/test_risk_repository_limits.py(3 例,用 literal_binds
编译 SQL,确保断言看到的是 201 而不只是"有 LIMIT"),并补日报与详情的标记用例;
风控文档 06 同步登记两个新字段与写接口的幂等要求。
2026-09-11 14:16:07 +08:00
Windows 47a5ca498c feat: complete advisor product suitability gates 2026-09-11 14:09:23 +08:00
lzf_0626 3ab7f33d64 fix(risk): 预警行带出 close_reason,日报的"误报原因"不再是空
docs/25 P2。核实确认报告准确,而且这一处缺口造成两个症状:

_alert_row 是**列表 / 详情 / 日报共用**的行构造器
(risk_repository.py:174 / :276 / :321),而它的字段列表里没有 close_reason。于是:

- 日报的"误报原因"分布恒为"未填写"
  (risk_daily_report_service.py:139 用 item.get("close_reason") or "未填写");
- :243 的明细里同一字段同样拿不到值。

断的是"关闭误报时写入原因"这条链路的**下半段**:risk_action_service.py:70 把原因赋给
lert.close_reason、也确实存进了库(app/model/fund.py:368 有该字段),只是读取时没带出来。

修法是一行:在 _alert_row 里补上 close_reason。三个调用点同时受益;读取方都是风控侧
接口(需要 risk:alert:read),不涉及客户可见面。

新增 tests/unit/repository/test_risk_alert_row_close_reason.py(2 条):关闭原因出现在行里;
未关闭时为 None 但**键必须在** —— 读取方靠 or "未填写" 兜底,键一旦缺失就永远只能走
兜底分支,那正是修复前的状态。

ruff / mypy(136 文件) / 639 unit+contract / 29 integration 全绿。
2026-09-11 13:51:37 +08:00
Windows 281e76e4b1 feat: add advisor portfolio analysis 2026-09-11 13:20:09 +08:00
Windows 60c4a49b9b feat: migrate advisor investment goals 2026-09-11 13:11:58 +08:00
Windows acb9175e31 feat: migrate advisor risk questionnaire 2026-09-11 12:57:07 +08:00
lzf_0626 554fbbcaab fix(identity): data_scope 不能写死 self——它让所有"看全量"的路径失效
实测发现:9002(risk_operator) 与 9003(admin) 都持有 all 级权限
(permission_scopes 里 audit:read='all'、risk:alert:read='all' 等),
但 context.data_scope **永远是 'self'**——identity_repository.py 第 47 行把它写死了,
而上面第 34-39 行刚算出每个权限的 scope 并取了最高(rank 表都写好了)。

后果:凡按 context.data_scope == "all" 判断能否看全量的路径全部走不通
(risk_query_service.py:198、risk_analysis_service.py:126、
risk_evidence_archive_service.py:218、risk_action_service.py:212),
风控专员拿着全量权限却查不到任何预警——这是上一轮三个工具"都返回空"的真正原因。

改为取该身份所有权限里的最高范围。没有 all 权限的角色行为不变
(实测 9001 customer 的 data_scope 仍是 self),因此不放松任何既有边界。

另加 tools/seed_risk_alert_demo_data.py:造 3 条演示预警覆盖三个只读工具的读路径
(高危待处理 / 中危调查中 / 低危已闭环),字段取值照 risk_scan_service.py:312-333 的
_build_alert 抄、状态用 OPEN_STATUSES,时间按库内约定存 UTC。注意该表 id 非自增,
所以脚本手工生成 id。

实测(9002 身份):
- 查看当前风险概览 → 未闭环 2 条、高危 2 条、待处理 1 条,含高优先级清单与证据摘要
- 查询高风险预警   → 2 条明细,命中 RW-002/003/007/012/015,附只读复核草案
- 查询 ALDEMO0001 的证据 → 完整快照事实 + 客户维度,并主动指出证据缺口
客服回归:9001 身份行为不变。
2026-09-11 12:54:51 +08:00
Windows 291cb7b58f feat: add advisor product evidence intake 2026-09-11 12:47:01 +08:00
zhangshy 47056792eb fix: 规范预警详情客户编号映射 2026-09-11 11:24:22 +08:00
zhangshy ecd0627396 feat: 调整通知接口字段并补齐风控配置清单 2026-09-11 10:35:24 +08:00
zhangshy a94d5c754d feat: 迁移奶龙风控业务模块与演示文档 2026-09-10 21:03:44 +08:00
lzf_0626 6516ccb385 feat: 第二版——接口契约对齐 docs/05,修复静默故障与数据库基线
相对第一版 46fc976 的完整变更。组员迁移对照表见 docs/20。

一、对外契约对齐 docs/05(破坏性,共 4 处,组员需按 docs/20 调整)
1) 配置发布端点改为文档规定的复数资源名:submit→validations、
   approve→reviews(需 body decision)、activate→activations、
   rollback→rollbacks;第一版这 4 个动词式路径 docs/05 从未定义过。
2) 错误码由 8 个笼统码改为 15 个具体语义码(FORBIDDEN→AGENT_PERMISSION_DENIED、
   UNAUTHORIZED→AUTHENTICATION_REQUIRED、CONFLICT→RESOURCE_VERSION_CONFLICT、
   RESOURCE_NOT_FOUND→RUN_NOT_FOUND/SESSION_NOT_FOUND 等),
   输入类错误状态码 400→422。
3) POST /api/v1/agent-runs 与 GET /api/v1/agent-runs/{run_id} 统一为
   {data, meta} 信封(data 内字段名与语义未变)。
4) 错误响应体统一为 {error:{code,message,retryable,field_errors}, meta:{trace_id}},
   不再返回 FastAPI 默认的 {"detail": ...}。

二、数据库基线与约束
新增 39 张表的基线迁移(链根)与联合唯一键纠偏(4 张表、删 8 增 4,幂等收敛);
撤下 config_release 的双人复核 CHECK(应用层已允许自审,审核节点保留,
自审如实写入 reviewer_id);记忆 active key 生成列与唯一键;
activate 开始记录 supersedes_release_id 使版本链可追溯。
docs/00 基线未修改,未重命名或删除任何表与字段。

三、修复会静默出错或无报错的缺陷
- 跑完集成测试后平台会静默失去生效配置:清理只删自己创建的版本,却没有恢复被它
  顶成 superseded 的原生效版本,且审计一并删除因而完全无痕,表现为所有工具被拒
  但没有任何报错。已修清理逻辑并加恢复。
- Worker 单轮异常导致进程退出;记忆抽取调用方的“事务已开始”异常;
  召回缓存丢失 degraded 标记;连接时区未生效导致 created_at/updated_at 差 8 小时;
  .env 与 os.getenv 密钥来源分裂导致“没有可用的已批准模型端点”。
- 记忆信号识别漏判与跨键误命中;SSE 未带 Accept 的协商行为。

四、功能补齐
记忆链路 P1/P2/P3(抽取、受控词表、召回与缓存、生命周期级联及投影事件)、
fin_* 场内交易只读 ORM 层、agent_intent_config 状态流转并在运行期真正生效、
限流(Redis 固定窗口、故障一律放行)、游标校验、trace_id 中间件、
示例业务 Agent fund_query_demo 与一键端到端验证脚本,以及审计/指纹/迁移状态工具。

五、文档与验证
新增 docs/19(业务 Agent 接入实操)、docs/20(第一版迁移指南)与 docs/evidence 证据;
docs/01/02/06/08/09/17 同步实现现状。

验证结果:ruff 通过、mypy 103 文件无错、unit+contract 447 passed、
integration 29 passed、acceptance_check --production 7 PASS、
demo_agent_e2e 9/9 PASS(含失败关闭反证)。
2026-09-10 15:55:54 +08:00
Codex b1497fd2c6 chore: initialize project repository 2026-09-09 21:55:37 +08:00