feat(投顾): 方案交付落点、可视化图表与推荐依据的大模型增强
交付落点
- 新增 GET /api/v1/users/me/advisor-contents(客户读**自己**已发布方案):
「发送给客户」原先只改数据状态、客户端没有任何页面或接口能读到它
- 客户端新增「我的投顾方案」页与导航入口
可视化(投顾结果区与客户页**共用** common/advisor-plan-view.js,避免两处漂移)
- 净值折线图(带坐标轴与网格)、组合业绩等权合成曲线(含区间收益与最大回撤)、
资产配置环形图与图例、组合构成条
- 修 num(null)=0 的假 0:Number(null)/Number('') 会得 0,导致「没数据」被渲染成 0.00%;
现一律显示「--」。同理管理费/起投未维护时按没数据处理,不显示 0
- 涨跌口径为「涨红跌绿」(A 股习惯),由 CSS 变量 --plan-up / --plan-down 集中定义
推荐依据接入大模型(可选,失败即回退)
- 新增 AdvisorReasonService:**只改文案,不参与选品**(候选池与排序在它之前已固定)
- 输入只允许是已算出的真实参数(风险等级、排序得分、区间收益、最大回撤、期限与流动性)
- 命中收益承诺词(保本/保证收益/稳赚/无风险…)整条丢弃并回退规则文案
- 未启用 / 缺密钥 / 超时 / 解析失败一律回退,推荐主流程不因模型不可用而失败
- 前端标注来源(AI 生成 / 规则生成)
数据与权限
- 客户角色补齐:绑 customer 角色、补建缺失的账户与交易段权限码(9060-9065)
- 净值全量同步(20 只产品),行情同步脚本按 --codes 分块(全量一次会被超时终止)
测试
- 新增 tests/unit/service/test_advisor_reason_service.py(10 项,专测三条合规边界)
- 前端模块自检纳入 service-request-module;补「两处共用同一渲染」回归测试
This commit is contained in:
@@ -39,6 +39,7 @@ async def test_portal_root_redirects_to_public_home() -> None:
|
||||
"/portal/customer/orders/",
|
||||
"/portal/customer/transactions/",
|
||||
"/portal/customer/cash-ledger/",
|
||||
"/portal/customer/advisor-plans/",
|
||||
"/portal/customer/risk-questionnaire/",
|
||||
"/portal/employee-console/login/",
|
||||
"/portal/employee-console/workspace/",
|
||||
@@ -172,20 +173,59 @@ def test_advisor_workspace_registers_documented_operation_endpoints() -> None:
|
||||
encoding="utf-8"
|
||||
)
|
||||
for endpoint_id in (
|
||||
"ADVISOR_PUBLISHED", "ADVISOR_GOAL", "ADVISOR_ANALYSIS",
|
||||
"ADVISOR_PUBLISHED", "ADVISOR_HISTORY", "ADVISOR_GOAL", "ADVISOR_ANALYSIS",
|
||||
"ADVISOR_ALLOCATION", "ADVISOR_RECOMMEND", "ADVISOR_CREATE_GOAL",
|
||||
"ADVISOR_REVIEW_RECOMMENDATION", "ADVISOR_PUBLISH_RECOMMENDATION",
|
||||
"ADVISOR_DELETE_RECOMMENDATION",
|
||||
):
|
||||
assert f"{endpoint_id}:" in source
|
||||
for label in ("组合分析", "资产配置", "生成推荐方案", "录入客户目标"):
|
||||
for label in ("组合分析", "资产配置", "生成推荐方案", "录入客户目标", "历史方案记录"):
|
||||
assert label in dashboard
|
||||
|
||||
|
||||
def test_advisor_plan_view_is_shared_by_both_surfaces() -> None:
|
||||
"""推荐方案的可视化渲染必须**只有一份**,投顾工作台与客户页共用。
|
||||
|
||||
两处都是同一份 `advisor_recommendation_plan`,各写一套必然漂移
|
||||
(改了一边忘了另一边)。这条测试同时守住「共享模块存在」与「两处都在用它」。
|
||||
"""
|
||||
view = (PORTAL / "common" / "advisor-plan-view.js").read_text(encoding="utf-8")
|
||||
assert "export function renderPlanProducts" in view
|
||||
assert "export async function hydratePlanView" in view
|
||||
assert "P002" in view
|
||||
for page in (
|
||||
"employee-advisor/dashboard/actions-module.js",
|
||||
"customer/advisor-plans/advisor-plans.js",
|
||||
):
|
||||
source = (PORTAL / page).read_text(encoding="utf-8")
|
||||
assert "advisor-plan-view.js" in source, page
|
||||
|
||||
|
||||
def test_advisor_history_module_exposes_plan_actions() -> None:
|
||||
"""历史方案记录里,推荐方案卡片要带审核/驳回/发送/删除四个操作。
|
||||
|
||||
四个动作对应三个端点(审核与驳回共用一个 reviews 端点,用 `decision` 区分)。
|
||||
少了 `ADVISOR_DELETE_RECOMMENDATION` 就只剩"能看不能删"。
|
||||
"""
|
||||
source = (
|
||||
PORTAL / "employee-advisor" / "dashboard" / "history-module.js"
|
||||
).read_text(encoding="utf-8")
|
||||
for label in ("审核通过", "驳回", "发送给客户", "删除"):
|
||||
assert label in source
|
||||
for endpoint in (
|
||||
"ADVISOR_REVIEW_RECOMMENDATION",
|
||||
"ADVISOR_PUBLISH_RECOMMENDATION",
|
||||
"ADVISOR_DELETE_RECOMMENDATION",
|
||||
):
|
||||
assert endpoint in source
|
||||
|
||||
|
||||
def test_advisor_dashboard_is_composed_from_feature_modules() -> None:
|
||||
source = (PORTAL / "employee-advisor" / "dashboard" / "dashboard.js").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
assert "./actions-module.js" in source
|
||||
assert "./published-module.js" in source
|
||||
assert "./history-module.js" in source
|
||||
config = (PORTAL / "employee-advisor" / "dashboard" / "advisor-config.js").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user