Commit Graph
292 Commits
Author SHA1 Message Date
zhangshy 522c9b19fe Merge remote-tracking branch 'origin/qyqy_develop' into RM2_develop 2026-09-13 21:09:09 +08:00
zhangshy 09c80575b7 延长风控手动扫描前端超时 2026-09-13 21:08:57 +08:00
lzf_0626 de208a039d docs(40): 补全流程验收实测到的接口形状差异
跑全流程 E2E 时连续踩到(都是调用方视角的真实摩擦点):

- 各端点的 data 形状不统一:账户看板 data.account + data.summary、持仓 data.holdings、
  成交明细 data.transactions、资金流水 **data.entries**、角色列表 data(裸数组)、
  知识列表 items(不套 data)。我写脚本时连续猜错三个字段名,一度误判"资金流水没记录"。
- 创建类接口返回 201 而非 200:下单 T002、创建会话 C001、访客令牌 V001 都是 201。

两条都写进"通用预期",并说明是实测结论。
2026-09-13 20:40:16 +08:00
lzf_0626 e9c148f005 fix(sync): 行情同步改为逐只容错,避免一只坏代码拖垮全量
全流程验收时发现的核心故障:**所有客户都无法下单**(任意产品都返回
503 FUND_QUOTE_UNAVAILABLE),而错误信息只说"某产品行情已过期",看不出根因。

根因链:
1) MarketQuoteSyncService.sync() 把库里 fund_manager='南方基金' 的 20 只产品
   打包交给 hq.py 的 loader;
2) 而 hq.py 的白名单校验是 ny(code not in SOUTHERN_FUND_CODES) -> raise,
   **一个代码不在名单里整批就抛错**;
3) 库里混着 510300 —— 它的真实管理人是华泰柏瑞(前面查费率时确认过),
   不在南方基金白名单里 -> 两个数据源全部 ValueError -> quotes=0;
4) 于是 in_market_price 长期不更新(当时只有 2 行、时间停在 08:19),
   下单的 MAX_QUOTE_AGE 校验全线失败。

修法:整批调用失败时**降级为逐只调用**,保留能取到的行情,把被拒代码记进
source_results(并拼进 error_type,让它在 dvisor_market_quote_source_run
审计行里可见,不必为此改表)。被拒代码通常意味着 und_manager 标注有问题,
值得人工核对。

不动 hq.py 的白名单:那是刻意的防误用设计(不许拿任意代码查南方行情),
该容错的是编排层。

验证:重跑 tools/sync_advisor_market_quotes.py 后不再是全批失败;
配合重跑 tools/seed_sim_account_demo 刷新 fin_market_price,
下单恢复 —— 510500、510300 均 HTTP=201 已成交。

⚠️ 仍未解决(数据供给缺口,需要产品/业务侧决定):
in_market_price 目前**只有演示种子脚本写**,没有生产同步链路。
后果是 20 只产品里只有 2 只(510300/510500)有行情、其余 18 只下单报
"缺少场内行情",而且行情一旦过期就没有任何机制刷新。
2026-09-13 20:39:36 +08:00
lzf_0626 ff37b0b476 docs(40): 记录知识治理端点的两处接口差异
联调时踩到并查明(更正前一版判断:不是接口故障,是调用方解包方式不对):

1) GET /api/v1/knowledge/list **不套 data 信封**,直接返回 {"items": [...], "count": N},
   而多数端点返回 {"code":…, "data": {…}}。按 data.items 解包会拿到 0 条,
   看着像"库里没数据"——我据此误判过一次,也因此在重灌时没能用列表接口批量删除,
   改为按 id 逐个 DELETE。
2) 上传是 POST /api/v1/knowledge/upload(/documents 会 405);
   DELETE /api/v1/knowledge/{id} 的语义是标记 expired + 投向量删除事件,不是物理删除。

两条都写进验收清单的"通用预期",避免下一个人重复踩。
2026-09-13 20:26:56 +08:00
lzf_0626 7fa4b4c4a5 feat(knowledge): 手册补「购买基金需要什么条件」一节并重灌
排查「购买基金需要什么条件」仍转人工的根因:**不是检索缺陷,是知识覆盖不足**。
实测该问法下手册 1.1 节其实排第 1(score=0.5416),但低于 MID_SCORE(0.55),
且与次优间隙只有 0.0195 < MIN_GAP(0.07),所以按置信度规则转人工 —— 规则本身工作正常。
对照:手册里有对应条目的问法分数是 1.0 / 0.7946 / 0.7932,链路健康。

补 1.6 节「购买基金需要什么条件」,只写有依据的事实(开户状态、风险测评与等级匹配、
按手交易的整数倍委托),不编造资金门槛或资质要求。

重灌过程顺带发现:GET /api/v1/knowledge/list 返回 0 块,而 fin_knowledge_meta 当时
有 22 行 active —— 管理端列表与实际入库不一致,所以删除只能按 id 逐个走
DELETE /api/v1/knowledge/{id}。这条接口的问题单独记,不在本次改动范围。

重灌结果:旧 22 块标记过期并投删除事件,新 23 块写入;问「购买基金需要什么条件」
命中 1.6 节 score=0.7793(≥ HIGH_SCORE 0.75),客服从转人工变为直接作答(4.08s)。
2026-09-13 20:25:57 +08:00
lzf_0626 fef4c826ff fix(worker): 知识写入 Milvus 必须探测字段名并补齐必填字段
背景:走 `POST /api/v1/knowledge/upload` 灌了 22 块场内基金知识,MySQL 全部写入成功、
向量事件也全部投递,却全部同步失败(重试 3 次进死信),客服检索不到新知识。
逐层定位出三个真问题,都在写入侧:

1) **字段名硬编码**。检索侧早已按 AGENTS.md 改用运行时探测
   (`app/core/knowledge_schema.py`:`doc_id`↔`knowledge_id`、`content`↔`snippet`),
   写侧却一直硬编码 `knowledge_id` / `snippet`。本机集合实际是
   `doc_id`/`content`/`chapter`/`visibility`/…(架构师那套 schema),于是
   `Attempt to insert an unexpected field knowledge_id` 整条失败。
   现在两侧共用 `resolve_schema` 的同一份映射表,调用方只用**逻辑**字段名;
   集合没有的字段(如另一套环境无 `intent`)跳过而不是报错。

2) **非 nullable 必填字段没给**。改完名字后报
   `Insert missed an field chapter to collection without set nullable==true`:
   集合里存在、调用方没提供的 VARCHAR 字段必须补值。VARCHAR 的判据用
   `params.max_length`,不必 import pymilvus 的枚举。

3) **补值不能一律空串**:`visibility` 留空会被检索侧
   `visibility == "public"` 的过滤整行排除。这条最隐蔽 —— `query` 查得到、`search`
   查不到,表现为「入库成功但客服永远答不出新知识」,比写入直接失败更难定位。
   现在按 `FIELD_DEFAULTS` 给有语义的字段默认值。

Worker 侧把 `fields` 的键改成逻辑名(`snippet` → `content`),上限表随之改名。

**测试**:原来的桩没有 `describe_collection`,所以这条路径从未覆盖到真机 schema
(这正是缺陷长期存在的原因)。现在桩提供两套真实 schema 并参数化,另加
「跳过集合没有的字段」「探测不出必要字段必须失败关闭」两个用例。

**验证**:22 块重新同步后 `fin_product_collection` 236 行、`visibility` 全为 `public`;
问「南方沪深300ETF 的起投金额是多少」命中新块 `score=0.7932`(≥ `HIGH_SCORE` 0.75),
客服从「转人工」变为直接作答,端到端 3.34 秒。

**另**:新增 `docs/43-场内基金产品手册(知识库入库版).md` —— 从 `docs/42` 抽出
**客户可见**的纯净内容(去掉全部内部决策备注)供入库;`docs/42` 保留为草稿与决策记录。
2026-09-13 20:08:31 +08:00
lzf_0626 6f397ab513 feat(data): 510300 回填真实值并在产品页披露;511810 口径查清
按项目方决定处理三件事。

## 510300(沪深300ETF)

决定:保留 fund_manager = "南方基金" 作演示口径,**不**改成"华泰柏瑞"
(改了它会退出 MarketQuoteSyncService 的同步范围,投顾与知识条目也不该再算自家产品),
但净值与费率改成真实值,并在产品页显式披露它不是本公司产品。

- 回填(tools/backfill_product_snapshot.py --codes 510300 --apply --overwrite):
  current_nav 4.500000 → 4.5794、current_nav_at 同步为 2026-09-11 15:00:00、
  management_fee_rate 0.5000 → 0.15、custodian_fee_rate 0.1000 → 0.05
- 产品页披露:mock-data.js 给这只加 product_note,详情页新增提示块渲染它
  ("本产品为同指数参考产品,非本公司发行的基金,仅用于功能演示")。
  同时把 mock 里这只的净值/费率也对齐真实值,避免出现"页面显示 mock 值、
  库里是真实值"两套数字。
- 产品详情页的 css/js 版本参数提到 20260913-5,避免旧缓存。

## 511810(货币ETF南方)

之前说它"三个数量级不一致"是**我比错了接口**:QUOTE_API 返的是**场内交易价格**
(货币 ETF 约 100 元/份),而 current_nav 的口径是 DWJZ 单位净值。
查证结果:库里 0.2661 正是 09-11 的真实 DWJZ,快照时间也对得上 —— 不是脏数据,是旧值。
口径由其余 19 只的一致性确定(current_nav == DWJZ)。
要不要刷新到最新(0.2332)属运维节奏问题,**本次未执行**。

## 最小交易金额

按项目方口径:一律**以产品字段为准**(100.00 元),不用「1 手 × 净值」推算金额 ——
后者会与字段值不一致,客户会看到两套数字。docs/42 的 1.1 节据此重写。

## 工具改名

tools/backfill_product_fees.py → tools/backfill_product_snapshot.py:扩展为
净值 + 费率统一入口,新增 --codes 限定范围(避免顺手把全部快照刷成最新,
全量刷新是 ProductHistorySyncService / MarketQuoteSyncService 的职责)。
安全设计不变:默认 dry-run、默认只补 NULL(净值永不为空,所以刷净值需显式
--overwrite)、写入走单事务。

门禁:ruff 通过 / mypy 249 文件 0 错 / 单元+契约 1377 passed 2 skipped。
2026-09-13 19:47:10 +08:00
lzf_0626 02ba1befc0 feat(data): 取真实费率并回填 fin_product;发现 510300 的身份问题
## 取费率

项目原有的行情适配器只有 names/quotes/history,没有费率。东财也没有可用的 JSON 接口
(`FundArchivesDatas.aspx?type=jjfl` 实测正文只有一句 `var apidata=`),
所以给 EastmoneyFundAdapter 加了 fetch_fees:抓 f10 基金概况页
(fundf10.eastmoney.com/jbgk_{code}.html)去标签后匹配字段名,取管理费/托管费/
销售服务费/申赎费,并顺带带回基金全称。取不到的基金不进返回(而不是给全 None),
免得调用方把"没抓到"当成"该基金确实没有费率"。

实测 20/20 全部取到。

## 回填

新增 tools/backfill_product_fees.py,默认 dry-run、默认只补 NULL:
- 只补空值:写入 38 个字段(19 只 × 管理费/托管费),已执行
- 已有值一律不动 —— `510300` 库里的 0.5000/0.1000 保持原样,要改需显式 --overwrite
- 写入走单事务

回填前 grep 确认:`fin_product.management_fee_rate` / `custodian_fee_rate`
**目前没有任何业务代码读取**(advisor 域那两个 fee 字段属于另一张表
advisor_product_contract),所以这次回填不改变任何现有行为。
要让客户真的看到费率,还需要接口与前端读它 —— 那不在本次范围。

## 两个数据问题(都指向 510300)

1) **它不是南方基金的产品**。数据源返回的基金全称是
   「华泰柏瑞沪深300交易型开放式指数证券投资基金」,而库里 fund_manager 写「南方基金」。
   其余 19 只全称都是"南方…"开头,只有它例外。
   影响面不只是知识库:MarketQuoteSyncService 按 fund_manager='南方基金' 筛选同步对象,
   所以它会被当成自家产品一起同步、一起展示。

2) **它的费率是照抄的**。库里 0.5000/0.1000,真实 0.15/0.05;而 0.50/0.10 恰好是
   159329/159382/159511/588890 这四只的真实费率。结合它的净值快照时间是当天
   (其余 19 只停在 09-11)、净值取整数 4.500000,判断是演示用途的人为设定。

这两条都由用户决定怎么处理,本次只记录、不擅自改(510300 的 fund_manager、净值、费率
三处原值都没动)。

## 其他

- tools/fetch_live_quotes.py 增加费率核对段落与 --no-fees(费率要串行抓 45KB/只,较慢)
- docs/42 用真实费率重写 1.2 节与新增 4.3 真实费率清单,3.1 节标出 510300 的身份问题
- docs/42 的数据缺口表更新:费率不再是缺口

门禁:ruff 通过 / mypy 249 文件 0 错 / 单元+契约 1377 passed 2 skipped。
2026-09-13 19:39:16 +08:00
lzf_0626 f3af00e645 fix(trade): 补 from None 修掉 ruff B904;附 integration 抢队列的复现结论
两件事,都源自组员 e3c316a(下单授权与适当性校验)合并之后:

1) ruff B904:app/service/trade_service.py 在 except 分支里抛
   SuitabilityMismatchError 时没写 from None/from exc,门禁因此变红。
   这里被捕获的是"风险等级字段解析失败",对上层没有诊断价值,
   用 from None 截断因果链即可。

2) tests/integration 出现 2 个失败,**与代码无关**,是常驻 Agent Worker 抢队列:
   - 带 Worker 跑:102 passed / 2 failed
   - 停掉 Worker 跑:104 passed(已复现两次)
   失败用例是 test_complete_run_rolls_back_every_write_on_outbox_conflict 与
   test_cancel_is_idempotent_and_terminates_original_request,都是 run 队列相关 ——
   Worker 会把测试创建的 run 领走并执行,测试自己的状态机就乱了。
   这正是 docs/20 与 AGENTS.md「跑验收前先停常驻 Worker」那条的实际复现,
   顺手把结论写进 commit 备查:判断 integration 红不红之前,先确认 Worker 在不在跑。
2026-09-13 19:30:59 +08:00
lzf_0626 d9d3420cf0 Merge remote-tracking branch 'origin/qyqy_develop' into qyqy_develop 2026-09-13 19:28:09 +08:00
lzf_0626 131effcfae feat(tools): 新增 fetch_live_quotes 拉真实行情并核对库里快照
项目本来就对接了东方财富真实行情(app/infrastructure/fund_market_adapter.py:
push2.eastmoney.com 的行情接口 + api.fund.eastmoney.com 的历史净值接口),
下单链路取 quote_price/quote_source 走的也是它。之前做知识草稿时用的是
fin_product 表里的快照,没有去核对真实数据源,这次补上。

新增 tools/fetch_live_quotes.py:
- 用项目自带的 EastmoneyFundAdapter 取数,不自己拼 HTTP、不引第三方行情库
- 产品范围与 MarketQuoteSyncService.sync 一致(南方基金 / SSE,SZSE / 上市)
- 逐只对比真实净值与 fin_product.current_nav,只报告差异、**不写库**
  (刷新快照是 ProductHistorySyncService / MarketQuoteSyncService 的职责)

实测结果(2026-09-13,20 只全部取到真实数据):
- 18 只完全一致(库里 6 位小数、数据源 4 位,是同一笔数)
- 2 只不一致:
  · 510300 沪深300ETF:库里 4.500000,真实 4.5794。库里快照时间是今天 08:19:37,
    其余 19 只都停在 09-11 15:00:00 —— 说明这是被人为改过的演示值(4.5 凑"1 手 450 元")。
    需要定:知识用真实值还是演示值;若交易按库里的 4.5 成交而知识说 4.5794,两处口径会打架。
  · 511810 货币ETF南方:库里 0.266100,NAV_API 给 0.2332,而 QUOTE_API(场内实时价)
    给 100.012 —— 差三个数量级,说明两个接口对这只的含义不同,需人工核实口径。

docs/42 随之更新:
- 产品清单改用真实净值,并加上当日涨跌与净值日期,注明取数来源与复现命令
- 3.1 节的沪深300ETF 条目改用真实值 4.5794,并列出两处待确认口径
- 新增 4.2 节记录本次核对结果与两只不一致项的判断依据
2026-09-13 19:27:58 +08:00
张胜宇 aa4b9bf367 Merge remote-tracking branch 'origin/qyqy_develop' into qyqy_develop 2026-09-13 19:26:31 +08:00
张胜宇 e3c316aafb Enforce trading authorization and suitability checks 2026-09-13 19:24:59 +08:00
lzf_0626 f72a545c39 refactor: 品牌统一为「南方财富」(项目方定)
背景:品牌名此前三处不一致 —— 后端 Agent 自称「奶龙基金」(customer_service_rules
的防诈骗/转人工话术、risk_agent 与 risk_analysis_service 的系统提示词)、前端全站
「南方财富」、闲聊提示词与知识素材「南方科技」。docs/36 已把它登记为"上报项目方后
待定",现按项目方决定统一为「南方财富」。

改动:
- app/core/customer_service_rules.py:P0 防诈骗话术与 P2 转人工话术里的品牌名
- app/service/agent/implementations/customer_service.py:COMPANY 常量,
  以及那处引用实测样本的注释(改为不绑定具体品牌名,免得下次改名又过时)
- app/service/agent/implementations/risk_agent.py:docstring、自我介绍、system prompt
- app/service/risk_analysis_service.py:SYSTEM_PROMPT
- app/worker/risk_scan_scheduler.py:--help 描述
- app/static/index.html(旧联调页 4 处)、portal/employee-risk/dashboard/index.html
- tests/unit/api/test_customer_service_test_page.py:同步断言(它断言的正是页面里的品牌名)
- tools/publish_chitchat_prompt.py:SYSTEM_PROMPT 改品牌;并修掉"存在即跳过"的检查
  —— 原来只判当前版本有没有这一行,于是改了文案也发不出去(脚本打印"无需发布"直接
  退出),没有任何提示。改为比对 system_prompt/user_prompt_template 内容。

闲聊提示词已重发为 release 308 / v5,生效内容为"你是南方财富的智能客服助手…"。

刻意未动:
- fin_product.fund_manager = "南方基金" —— 它被 market_quote_sync_service 与
  product_history_sync_service 当过滤条件使用,改名会让同步链路查不到产品
- knowledge_search_service.py 注释里引用的知识库实际标题「南方科技有限公司…」
- docs/客服docs 下的历史素材与 docs/ 下的过程记录(属历史留痕)

⚠️ Milvus 里的知识条目仍写「奶龙基金」(RAG-*/NF-*)与「南方科技」(PROD-*),
属知识数据,需重灌才能统一;本轮不动。

同时:
- docs/40 把品牌条目标为已处理,并补上知识库缺口的现状
- 新增 docs/42-场内基金知识条目草稿.md:按 fin_product 的 20 只产品生成,
  含通用交易规则与产品清单;费率等缺失字段一律标"以交易页面为准",未编造数字。
  **该文件是草稿,未入库**,待审核后走 POST /api/v1/knowledge/documents 灌库。
2026-09-13 19:17:56 +08:00
lzf_0626 52ae38efd7 docs(40): 补齐前端验收清单(投顾/运营页、访客浮窗、Worker 前置)
- 关键修正:第 0 节原先写「停常驻 Worker」,方向是反的 —— 客服对话**必须**有
  Worker,否则 agent_run 停在 queued,前端只显示超时。补上排查第一步(查最新
  那行的 status)与本机实测延迟(端到端 4.1-4.8 秒,受理只占 0.05 秒)。
- 新增第 6 节投顾工作台:数据口径(本人 + 名下归属客户,不用 data_scope)、
  方案书审核与发布必须由管理员做(admin=True)、归属数据不随环境走。
- 新增第 7 节运营工作台,含客户/风控访问被 403 拦的实测。
- 新增 2.5 节访客客服浮窗:访客令牌的角色与权限、访客与客户走不同检索工具名。
- 新增第 9 节「看起来像 bug、其实是设计」:置信度阈值 0.75/0.55/0.07 与三条实测
  分数逐条对应(0.8203 直接答;0.6818 但间隙 0.0034 转人工;0.5221 转人工),
  说明「总转人工」的根因在知识库内容而不是代码;另两条是投顾接口非越权、
  方案书复核环节。
- 第 1 节更新为 18 个页面路径 + 12 个静态资源,全部 200(实测值)。
- 第 10 节补入本轮修的 4 个问题(角色落点、访客白名单、mock 声明、发布脚本丢提示词)。
- 新增第 11 节已知未修:知识库无场内 ETF 内容、fin_knowledge_meta 与 Milvus 不一致
  (列表接口实测 count=0)、品牌名三处不一致、ADVISOR_GOAL/ANALYSIS 声明未用等。
- 附录补演示账号,并注明 9006/9020 的口令不在 set_user_password.py 的演示规则里。
2026-09-13 19:10:21 +08:00
lzf_0626 680c2a0749 docs(AGENTS): 补 Worker 常驻与发布脚本继承范围的踩坑记录
- 新增一条:客服/风控对话必须有常驻 Worker(python -m app.worker)。没有它时
  agent_run 停在 status=queued、worker_id 为空,前端只会显示"客服响应超时/繁忙"
  —— 看起来像链路慢,实际是没人处理。附排查第一步(查 agent_run 最新那行)
  与本机实测延迟(端到端 4.1-4.8 秒,其中受理只占 0.05 秒)。
- 更新发布脚本那条:继承范围必须覆盖全部三张受管表(此前只搬 platform_config_item,
  把 customer_service_chitchat 提示词静默漏在旧版本里),并写明知识类意图要同时发
  search_knowledge 与 query_knowledge。
2026-09-13 19:04:29 +08:00
lzf_0626 2098185477 fix(portal): 客服浮窗轮询参数调整,压感知延迟并留足超时预算
现象:访客在公开页问客服,等约 9.5 秒后报「客服响应超时」。
根因不在链路快慢 —— Agent Worker 没在跑,run 一直停在 queued,
前端把「没人处理」呈现成了「超时」。

启动 Worker 后实测同一条链路(受理 0.05s + 意图分类 + 知识检索 + 落库):
4.11s / 4.09s / 4.82s,三条全部 succeeded。模型已经是 deepseek-flash,
延迟主要来自一次意图分类加一次 embedding,不是模型选型问题。

不过原来的轮询参数余量确实偏薄:350ms 后首次、之后每 700ms 一次、共 14 次,
约 9.5 秒封顶,后端稍一抖动就撞上;而且平均要多等半个轮询周期才看到结果。
改为 300ms 后首次、之后每 500ms 一次、共 40 次(约 20 秒):
感知延迟压到半秒内,同时给模型与检索抖动留出余量。
超时文案也从「客服响应超时,请稍后重试」改为「客服繁忙,暂时没能给出答复」,
不再暗示是响应慢。代码注释里写明:若仍然超时,先确认 Agent Worker 已启动。
2026-09-13 19:03:49 +08:00
lzf_0626 d4a895c643 fix: 修好访客客服浮窗、投顾工作台数据口径,恢复访客页来源声明
组员这次提交的两套新页面方向都对,但各有一处"接不上"的地方,这里补齐。

1) 访客客服浮窗(此前一问即失败)
   客服 Agent 让访客走 query_knowledge(访客令牌的角色是 visitor、权限只有
   agent:run + knowledge:query),但发布配置里三个知识意图只发了 search_knowledge,
   于是 ToolExecutor 直接抛 ForbiddenAgentError,而客服代码对白名单失败是
   「必须冒泡」的 —— 访客拿不到任何回答,登录客户侧却完全正常。
   发布脚本的知识类意图改为同时发 search_knowledge 与 query_knowledge:
   访客走前者、客户走后者,缺任一条对应人群就失败关闭。
   (suitability_check 不发 query_knowledge:访客意图白名单不含它,访客到不了。)

2) 发布脚本会静默丢提示词
   旧写法只查 platform_config_item 就当作"继承",而 config_release 是整版本替换
   语义,新版本没带上的行等于被删除 —— 实际把 customer_service_chitchat 提示词
   漏在了旧版本里(admin 端只在激活时打一句 stderr 警告)。
   改为走 ConfigReleaseService.effective_snapshot() 读全三张受管表,补上提示词
   搬运(version 重分配、带上 input_schema/output_schema),并在激活后硬校验
   配置项与提示词条数,条数不符即失败退出。
   (model_routing_rule 本环境为空;不为空则直接中止,不假装支持。)
   丢失的那条提示词已按原文恢复,active 版本现为 9 条配置项 + 1 条提示词。

3) 投顾工作台永远为空
   published() 取的是 customer_id == 自己 user_id,而投顾是员工账号、不可能是
   客户;且只认 advisor_recommendation_plan + approved,而投顾交付的主产物是
   investment_goal_book,发布后状态是 published。三重不匹配下页面永远显示空态。
   改为按「本人 + sys_customer_assignment 里名下归属客户」过滤(不用 data_scope:
   投顾因持有 all 级权限会把整个身份的 scope 抬到 all,那会放开到全部客户),
   并覆盖两类 content_type 与两种已发布取值。
   实测:投顾可见归属客户 9001 的方案书,客户仍只见自己的,风控仍 403。

4) 访客页把"演示数据"声明删了但假数据还在
   mock-data.js 的 MOCK_SOURCE_NOTICE 与两个页面的 data-source-notice 区块被删除,
   而 MOCK_PRODUCTS/MOCK_RANKING_CHANGE 仍在渲染(详情页含历史净值曲线)。
   恢复声明常量、页面区块与样式,并给 products/product-detail 的 link 与 script
   加上版本参数 —— 此前没有版本号,浏览器会命中旧缓存,改动看不见。

其他:投顾页显示交付物类型与客户编号(后端新返回的字段),README 补上投顾页
数据口径、访客/客户两条检索工具的差别,以及"渲染 mock 必须带来源声明"的约定。
2026-09-13 18:54:09 +08:00
张胜宇 b9aafce120 Merge remote-tracking branch 'origin/qyqy_develop' into qyqy_develop 2026-09-13 18:33:34 +08:00
张胜宇 e740a58e9d 前端2 2026-09-13 18:31:38 +08:00
zhangshy c93c490814 Merge remote-tracking branch 'origin/qyqy_develop' into RM2_develop 2026-09-13 16:22:53 +08:00
lzf_0626 4fdf1762e6 docs: 验收清单改为面向正式前端 /portal/ 2026-09-13 16:22:25 +08:00
zhangshy 8914b6ed99 Merge remote-tracking branch 'origin/qyqy_develop' into RM2_develop 2026-09-13 16:21:35 +08:00
zhangshy 7ab49c3f7f 新增风控模块需求说明书和接口文档 2026-09-13 16:20:52 +08:00
lzf_0626 5b6eaddc4f docs: AGENTS.md 补正式前端口径与最新测试基线;portal.py 标注为调试工具
- 新增前端一节:app/static/portal/ 挂载在 /portal/,四套页面,端点表在 common/api-client.js,
  启动用 uvicorn app.main:app(模块级变量是 app 不是 application)
- 测试基线更新为 2026-09-13 实测:mypy 249 文件 0 错 / 单元+契约 1376 passed / 集成 104 passed
- tools/portal.py 文件头标注降级为跨角色联调工具
2026-09-13 16:20:39 +08:00
lzf_0626 f4eaa2f631 fix(seed): 客户种子改为已开户;portal.py 标注为调试工具
- seed_test_rbac.py 把 fund_account_status 硬编码成 'closed',与 create_test_user.py
  的 {customer: 已开户, employee: closed} 口径不一致,导致种子建的客户永远未开户
- 影响:GET /users/me/account/dashboard、/users/me/cash-ledger 恒返回 404,
  正式前端 T001/T009 两个页面打不开(端点本身是好的)
- 改为按 user_type 取状态;配合 tools/seed_sim_account_demo.py 开虚拟资金账户后
  T001/T006/T007/T009 全部 200
- tools/portal.py:正式前端已在 app/static/portal/,本工具降级为跨角色联调工具
2026-09-13 16:19:54 +08:00
lzf_0626 f7f43742fd chore: 移除误提交的 .agents/skills 与 skills-lock.json(AI 助手配置,与项目无关)
- 这 15 个文件随 e38ece3 前端提交一起进了仓库,内容与本项目无关
- 用 git rm --cached 从索引移除(本地文件保留),并加入 .gitignore
2026-09-13 16:17:00 +08:00
张胜宇 e38ece32bf 前端提交 2026-09-13 15:56:54 +08:00
张胜宇 0ee3e894e7 merge: integrate latest qyqy_develop + push docs/41 2026-09-12 17:14:36 +08:00
ZSY_docs 554d125190 docs(41): 客服 Agent 前端开发约束 v1.0(团队强制规范)
新增 docs/41-客服Agent前端开发约束_v1.md(720 行 / 68 个标题 / 13 节 + 3 附录):

- §2 技术栈:原生 JS + CSS + ECharts;禁 React/Vue/TS/Tailwind(与底座现有 app/static/index.html 风格一致)

- §3 目录结构:portal/<role>/<page>/ 三层命名空间

- §7 接口调用:api-client.js 封装;trace_id + JWT 自动注入;4xx 不重试

- §8 权限 RBAC:9060-9065(§T 段)映射可见性;前端不发明角色

- §11 看板专属:模块归属 portal/customer/dashboard/;4 模块按 T001 字段拆;60s 缓存 + 仅手动刷新;3 状态必齐

- §13 验收清单:通用 10 项 + 看板 9 项 + 自动化 3 项

本文件与 docs/40-前端验收清单.md 互补(约束 ≠ 验收),编号 41 不撞号
2026-09-12 17:04:37 +08:00
zhangshy 4f495c6048 修正风控前端文档重命名后的引用 2026-09-12 16:58:31 +08:00
zhangshy 6039ee3d89 Merge remote-tracking branch 'origin/qyqy_develop' into RM2_develop 2026-09-12 16:56:46 +08:00
zhangshy d919b2ae5c 重命名风控前端合并文档并更新引用 2026-09-12 16:56:27 +08:00
lzf_0626 bbe575f38f fix(portal): 模态打不开时立刻 resolve,避免 await 永久挂起(表现为点击没反应)
- askConfirm/askText 在 openModal 失败时立即 resolve(false)/resolve(null):
  此前 Promise 永不 resolve,await 会一直挂着,用户看到的就是点了没反应
- toast/openModal 在容器缺失时给出明确提示而不是静默失败
- 顶栏显示页面构建时间(取 portal.py 修改时间),用于一眼确认加载的不是缓存旧页
2026-09-12 16:41:26 +08:00
lzf_0626 280c8e5026 docs: 清单风控章节按 17- 前端合并约束重写(15 项,含实测证据与三个坑) 2026-09-12 16:31:55 +08:00
lzf_0626 167a4e7162 Merge remote-tracking branch 'origin/qyqy_develop' into qyqy_develop 2026-09-12 16:30:56 +08:00
lzf_0626 6155f4589e feat(portal): 按风控前端合并约束重写风控工作台
- 概览五指标(总量/等级/待处理/超时/重点预警)
- 预警队列:每页 5 条、风险等级优先、8 项筛选、游标分页、稳定行高与省略号
- 预警详情弹窗:23 个 alert 字段 + 客户信息 + 五个处置动作
- 处置:确认接收(二次确认)/进入调查/关闭误报(必填理由)/结案(必填结论)/升级(必填原因)
- 八类证据(customers/products/transactions/capital_flows/holdings/login_records/
  alerts/notifications)+ 五项筛选;注意是 holdings 不是 positions
- 通知记录、日报 SSE 流式生成 + 内容编辑 + 多邮箱发送
- 新增 Toast + 模态框,替换全部 17 处原生 alert(17- 文档明令禁止原生弹窗)
- 风险等级筛选值改用 高/中/低(预警对象用「高」,概览 levels 用「高风险」,口径不一致)
2026-09-12 16:30: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
zhangshy a03d5e33c3 更新风控邮件通知与定时扫描文档 2026-09-12 16:30:01 +08:00
zhangshy 001ba065df Merge remote-tracking branch 'origin/qyqy_develop' into RM2_develop 2026-09-12 16:12:02 +08:00
lzf_0626 6e916aab03 Merge remote-tracking branch 'origin/qyqy_develop' into qyqy_develop 2026-09-12 16:08:39 +08:00
lzf_0626 76923d7e8b fix(portal): 补齐写操作的必填 body(风控升级/解决、场外六个写接口)
- 风控:escalations 要 reason、resolutions 要 resolution(字段名不同),
  且真实顺序是 先确认接收 才能升级/解决(否则 409 请先确认接收预警)
- 场外:六个写接口都必填 operator_id(防伪校验,须等于当前登录用户),
  confirmations 还要 decision(中文枚举)、notifications 还要 notification_type、
  recalculate 要 fund_code+application_date;门户自动带当前 user_id
- 实测:recalculate 200 code=0;不传 operator_id 必得 422(证明该字段必须)
- 清单 §3/§4 更新为实测结果并列出各写接口的必填字段表
2026-09-12 16:08:38 +08:00
ZSY_merge e25296f30a merge: integrate origin/qyqy_develop (552055ee) 2026-09-12 16:05:24 +08:00
lzf_0626 552055ee2d fix(portal): 修风控预警列表 422(limit 上限是 5,改为不传);运营邮件改用 page/page_size;审计 limit 钳制到 1-100
- risk/alerts 传 limit=20 会 422 query.limit<=5,导致整张表格渲染不出来、
  行内确认/升级/解决按钮随之消失 —— 这正是验收清单 3-3/3-6~3-9 看不到功能的原因
- 实测列表返回 2 条预警;处置接口有状态前置条件(409 = 当前状态不允许)
- 清单 3-3/3-6 更新为实测结果并写明 limit 上限与状态机约束
2026-09-12 16:03:22 +08:00
ZSY_merge_bot 4c651ccce1 merge: integrate latest origin/qyqy_develop (frontend acceptance checklist + portal flow wizard) 2026-09-12 15:58:08 +08:00
zhangshy eb4e895e4e 修复高风险预警邮件通知并补充测试 2026-09-12 15:56:50 +08:00
张胜宇 ebc3fe4cbe feat(§T): 账户看板 + 场内模拟交易 9 端点(用户自助首版)
新增 §T 用户自助段(docs/05 §19 新号段 7 个 = A×40/C×7/K×4/M×4/O×3/R×4/T×9):

- T001 GET /api/v1/users/me/account/dashboard  — 账户/资金/持仓/盈亏汇总

- T002 POST /api/v1/users/me/orders  — 委托提交(首版 market 立即全额成交)

- T003 / T004 / T005  委托列表/详情/撤单

- T006 GET /api/v1/users/me/holdings  — 持仓列表(含市值/盈亏/当日盈亏)

- T007 / T008  成交记录列表/详情

- T009 GET /api/v1/users/me/cash-ledger  — 资金账本

要点(与 docs/00 §6.6 一致):

- 首版市价委托立即全额成交,不实现撮合队列/部分成交;T005 撤单首版对任何在场委托返回 ORDER_NOT_CANCELLABLE (409)

- 价格来源复用 base FundQuoteService;service 层不二次封装(满足 AGENTS 第 2 条)

- 首版风控 3 条硬性:产品可交易、客户适当性、持仓比例上限(fin_market_price 缺失或过期 → 拒绝买入)

- 数据库零修改:10 张 fin_* 表全部 docs/00 既定,本批 PR 改列类型与可空性均 0;底座实际偏差(id 无 AUTO_INCREMENT、所谓'生成列'是普通 NOT NULL)由 service _next_id / 业务派生值补偿

注册 API:9 端点均注册进 app.main;user=9001(cust)'s id 写账

权限码(tools/seed_test_rbac.py 同步登记 + CUSTOMER 全量):

  9047 account:read:self

  9048 trade:order:create

  9049 trade:order:read

  9050 trade:order:cancel

  9051 holding:read:self

  9052 trade:txn:read

错误码(app/core/errors.py + docs/05 §3.6 + tests/unit/core/test_errors.py DOCUMENTED 三方同步):

  404 ACCOUNT_NOT_FOUND / ORDER_NOT_FOUND

  409 ORDER_NOT_CANCELLABLE

  422 INSUFFICIENT_FUNDS / INSUFFICIENT_HOLDING / HOLDING_RATIO_EXCEEDED / SUITABILITY_MISMATCH / PRODUCT_NOT_TRADABLE

  503 FUND_QUOTE_UNAVAILABLE(可重试)

新增:app/api/controllers/trading.py / app/api/schemas/trading.py / app/service/trade_service.py / tools/seed_sim_account_demo.py / tests/unit/service/test_trade_service.py(unit×8) / tests/contract/test_trading_endpoint_contract.py(contract×11)

修改:app/main.py(挂载 controller) / app/core/errors.py(10 新异常类) / tools/seed_test_rbac.py / docs/05-接口文档.md(§19 T001-T009 + §3.6 9 新码) / tests/unit/core/test_errors.py(DOCUMENTED 同步)

门禁:pytest tests/unit tests/contract 1313 passed (+19 新增) / ruff all clean / 三道守卫全过
2026-09-12 15:50:37 +08:00
lzf_0626 49ebda4cb7 docs: 新增前端验收清单(64 项,逐项给出预期结果,区分实测与按契约) 2026-09-12 15:48:31 +08:00
lzf_0626 2525fbdad9 feat(portal): 推广材料补成完整流程向导,并改为按 body.code 判成败
- 投顾视图:创建 -> 补结构化输入(含七项费率) -> 生成 -> 投递 -> 查询
  生成后自动把 material_version_id 填进投递框
- 管理员视图:新增推广材料审核(promotion:review 只给管理员,职责分离)
- 统一结果渲染改看 body.code:本平台业务失败也返回 HTTP 200
  (生成失败是 HTTP 200 + code=422 + '材料内容未通过合规校验')
- 输入骨架刻意不预填业绩(performance_info 有 show_* 开关可关),
  费率填占位文本以便跑通流程;真实材料必须换成真实值
2026-09-12 15:45:09 +08:00