Files
group_fqcd_jr/docs/evidence/20260919-t7c-profile-tier.json
T
张胜宇 9675df8453 chore(sync): zsy_developcc 全量同步至 qyqy_develop(W26 口径)
- 分支内容对齐 qyqy_develop b6ec3aa,树完全一致(同步后 git diff 为空)
- 覆盖本轮全部交付:客服 Agent 重构(安全路由 / 五出口 / 记忆与画像 / RAG 全链路)
  + 开发文档 62 份编号体系(D1.1 v1.17 索引)
  + 新增 D2.10-客服Agent端到端答辩文档-2026-09-21.html
- 基线:e239eb7(2026-09-17 品牌口径统一快照),本提交为其直接后继
2026-09-21 21:26:30 +08:00

93 lines
5.7 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"evidence_id": "20260919-t7c-profile-tier",
"generated_at": "2026-09-19T17:29:52+08:00",
"session": "D1.6 §4.32 / D2.1 v6.19",
"what": "D-11 落地:把「客户分层」接通(sys_user.customer_tier -> 画像快照 -> 客服作答),过程中定位并修掉真正的根因 —— 画像快照被残片覆盖 + JSON 列二次编码",
"how": "连库取证 profile_snapshots 全版本 -> 对读两个快照写者的产出形状 -> 修 -> pytest / 冒烟 / 真 HTTP 复验",
"corrected_diagnosis": {
"previous": "D-11 原判:sys_user.customer_tier 全库 NULL(从未种过)+ 画像快照按设计排除该字段",
"actual": "主因是两个写者互相覆盖:ProfileAssemblyService.rebuild_profile() 就地拼一个只含 PROFILE_OWNED_FIELDS + generated_at 的四字段残片,同样置 is_current=1,把权威快照里的 customer_tier / total_asset / behavior_score / assessment_* 全部抹掉。实测客户 9001 当前快照被覆盖到只剩 {risk_tags, generated_at, investor_type, investment_horizon, preferred_asset_class}(版本 2..16 全是这种形状)。「字段没种」与「写侧排除」两条也都成立,但都是次因。",
"evidence_query": "SELECT version, is_current, snapshot FROM profile_snapshots WHERE customer_id=9001"
},
"fixes": [
{
"id": "D-12a",
"file": "app/repository/profile_repository.py",
"change": "_SQL_PROFILE 增加 LEFT JOIN sys_user 取 customer_tier(fin_customer_profile 无该列;用 LEFT JOIN 使缺账号行时画像仍能生成)"
},
{
"id": "D-12b",
"file": "app/service/profile_generation_service.py",
"change": "build_snapshot 纳入 customer_tier;REQUIRED_SNAPSHOT_FIELDS 取消 customer_tier 例外(写侧与读取侧白名单必须等集)"
},
{
"id": "D-12c",
"file": "app/service/profile_assembly_service.py",
"change": "rebuild_profile 改走 build_snapshot()(唯一实现),不再自拼残片;字段所有权约束的是「写 fin_customer_profile 表」,不是快照字段集合"
},
{
"id": "D-13",
"file": "app/service/profile_assembly_service.py",
"change": "新增 PROFILE_JSON_FIELDS:preferred_asset_class / risk_tags 是 JSON 列,不再套 _as_text(会把列表序列化成字符串再被 JSON 列二次编码);risk_tags 改存数组"
},
{
"id": "D-13b",
"file": "app/service/profile_generation_service.py",
"change": "_as_list 兜底 JSON 字面量 \"null\"(当作空),并保留 json.loads 已脱引号的值"
},
{
"id": "D-14",
"file": "tools/seed_profile_demo.py",
"change": "① 客户分层写进权威列 sys_user.customer_tier;② 新增 user_facts 种子(preference:horizon / preference:asset_class)—— 这两个字段由重建服务独占、无事实即清空,写主表列会被下一次重建抹掉;③ 快照改由 ProfileGenerationService.generate() 生成,不再手拼"
}
],
"before_after": {
"snapshot_9001_before": {
"investor_type": "C1",
"risk_tags": null,
"generated_at": "...",
"investment_horizon": null,
"preferred_asset_class": null
},
"snapshot_9001_after": {
"risk_tags": [],
"total_asset": "60000.00",
"customer_tier": "gold",
"investor_type": "C1",
"behavior_score": 22,
"last_active_at": "2026-09-16T09:27:34+00:00",
"trading_frequency": "low",
"assessment_expired": false,
"investment_horizon": "short_term",
"preferred_asset_class": [
"money_fund"
],
"assessment_valid_until": "2027-07-16T09:27:34+00:00"
},
"answer_before": "您的风险测评等级是 保守型(C1)。",
"answer_after": "您的风险测评等级是 保守型(C1)。 / 投资期限偏好:短期(1 年以内)。 / 交易频率:较低。 / 偏好资产类别:货币基金。 / 客户分层:金卡。"
},
"authority_layout": {
"customer_tier": "sys_user.customer_tier(权威列,本次补种 6 行)",
"user_facts": "preference:horizon / preference:asset_class(12 行 = 6 客户 x 2)",
"fin_customer_profile": "investor_type / investment_horizon / preferred_asset_class / trading_frequency / total_asset / behavior_score / risk_tags",
"note": "investment_horizon / preferred_asset_class 由 ProfileAssemblyService 独占,无 user_facts 即清空 —— 所以种子的权威来源是 user_facts,不是主表列"
},
"gates": {
"pytest": "1746 passed / 0 failed / 2 skipped(较上一轮 +4 条新用例)",
"ruff": "22(= 基线)",
"mypy": "3(= 基线)",
"smoke": "tools/e2e_smoke_test.py --read-only -> 31/31 全通过",
"http_probe": "_eval_harness/http_probe.py -> 11/11 全 succeeded"
},
"tests_added": [
"tests/unit/service/test_profile_generation_service.py::test_snapshot_carries_customer_tier_from_its_authority",
"tests/unit/service/test_profile_generation_service.py::test_json_null_literal_is_not_a_preference",
"tests/unit/service/test_profile_generation_service.py::test_json_string_value_loses_its_serialization_quotes",
"tests/integration/test_profile_snapshot_completeness_mysql.py::test_rebuild_writes_a_complete_snapshot_not_a_partial_fragment(含 JSON 列二次编码断言)"
],
"open_items": [
"分层档位与 total_asset 不一致:6 行里 5 行的 tier 与公开门槛(金卡 50 万+/白金 200 万+/钻石 600 万+/尊享 1000 万+)对不上;但 agent 只读 sys_user.customer_tier、不用资产推,因此回答本身不自相矛盾 —— 口径待定",
"risk_tags 是「自述事实重算」字段:种子写进列的标签在首次记忆重建后会被清空(9001 实测由 [conservative] 变 []);要演示标签需种 user_facts 自述项"
]
}