- Added `ThresholdRepository` for managing customer loss threshold configurations and notifications. - Introduced `threshold_service` to handle loss threshold alerts based on customer portfolio performance. - Enhanced `customer_prompts` to include new intent for querying product net values. - Updated `customer_service` to integrate new threshold alert functionality into existing workflows. - Implemented `sanitize_postprocess` for improved compliance handling in customer interactions. - Enhanced course documentation to reflect updates in advisor training modules and interactive elements. This update significantly improves the customer experience by providing proactive loss threshold notifications and enhancing the overall service framework.
448 lines
26 KiB
HTML
448 lines
26 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>代销平台 API 深潜 · 交互导览</title>
|
||
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,400;12..96,600;12..96,700;12..96,800&family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700;1,9..40,400;1,9..40,500&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||
|
||
<link rel="stylesheet" href="styles.css">
|
||
|
||
<style>
|
||
:root {
|
||
--color-accent: #E06B56;
|
||
--color-accent-hover: #C85A48;
|
||
--color-accent-light: #FCEEEA;
|
||
--color-accent-muted: #E89A8A;
|
||
}
|
||
</style>
|
||
|
||
<script src="main.js" defer></script>
|
||
</head>
|
||
<body>
|
||
|
||
<nav class="nav" id="nav">
|
||
<div class="progress-bar" id="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
|
||
<div class="nav-inner">
|
||
<span class="nav-title">代销平台 API 深潜</span>
|
||
<div class="nav-dots" id="nav-dots" role="tablist">
|
||
<button class="nav-dot" data-target="module-1" data-tooltip="v0.1 路由" role="tab" aria-label="模块 1: v0.1 路由"></button>
|
||
<button class="nav-dot" data-target="module-2" data-tooltip="平台鉴权" role="tab" aria-label="模块 2: 平台鉴权"></button>
|
||
<button class="nav-dot" data-target="module-3" data-tooltip="脱敏开关" role="tab" aria-label="模块 3: 脱敏开关"></button>
|
||
<button class="nav-dot" data-target="module-4" data-tooltip="接缝与草案" role="tab" aria-label="模块 4: 接缝与草案"></button>
|
||
</div>
|
||
</div>
|
||
</nav>
|
||
|
||
<main id="main">
|
||
<section class="module" id="module-1">
|
||
<div class="module-inner">
|
||
<p class="eyebrow animate-in">模块 1 · 路由地图</p>
|
||
<h1 class="module-title animate-in">v0.1 路由:<br>customers / products / advisors / compliance</h1>
|
||
<p class="module-lead animate-in">
|
||
代销平台 API 是「读 Core 模拟库」的
|
||
<span class="term" data-definition="REST = 用固定 URL + HTTP 方法(GET/POST)读写数据的约定,浏览器和 App 都按这个格式发请求。">REST</span>
|
||
门面。路由按<strong>业务域</strong>切分,不用 <code>/api/platform/*</code> 前缀。
|
||
和 Agent 对话线(<code>/api/chat</code>)是两条平行轨道——指挥 AI 加「查持仓」时,先确认走哪条。
|
||
</p>
|
||
|
||
<div class="screen animate-in">
|
||
<h2>四块 canonical 域(以本文为准)</h2>
|
||
<div class="pattern-cards">
|
||
<div class="pattern-card">
|
||
<h3>/api/customers/*</h3>
|
||
<p>客户 L0 档案、持仓、流水。平台客户 REST 薄路由(<code>customers.py</code>) → <code>platform/customer_service</code> → <code>core_ro</code>。</p>
|
||
</div>
|
||
<div class="pattern-card">
|
||
<h3>/api/products/*</h3>
|
||
<p>产品列表、净值曲线。理财师/客户看产品详情都走这里,不另开 Agent Tool HTTP。</p>
|
||
</div>
|
||
<div class="pattern-card">
|
||
<h3>/api/advisors/*</h3>
|
||
<p>理财师名下客户归属。advisor 只能查本人 roster,风控/分析员可全量只读。</p>
|
||
</div>
|
||
<div class="pattern-card">
|
||
<h3>/api/compliance/*</h3>
|
||
<p>适当性判定等合规读接口。<strong>canonical</strong>:Agent 合并期改调同一 Service,不保留第二套路径。</p>
|
||
</div>
|
||
</div>
|
||
<div class="callout callout-accent">
|
||
<strong>重复能力以谁为准?</strong> 以本平台 API 为准。Agent 侧已有同能力 Tool 时,合并期改调 <code>app/service/platform/</code>,旧路径仅过渡。
|
||
</div>
|
||
</div>
|
||
|
||
<div class="screen animate-in">
|
||
<h2>三层分工(别让 AI 在路由里写 SQL)</h2>
|
||
<div class="flow-animation" data-steps='[
|
||
{"highlight":"flow-actor-1","label":"HTTP 请求进入 平台客户 REST 薄路由(customers.py)"},
|
||
{"highlight":"flow-actor-2","label":"get_platform_auth_context:验 JWT + 归属","packet":true,"from":"1","to":"2"},
|
||
{"highlight":"flow-actor-3","label":"platform_service:组装、分页、脱敏开关","packet":true,"from":"2","to":"3"},
|
||
{"highlight":"flow-actor-4","label":"core_ro:SELECT 只读,不感知 HTTP","packet":true,"from":"3","to":"4"},
|
||
{"highlight":"flow-actor-1","label":"ok() 统一外壳返回 JSON","packet":true,"from":"4","to":"1"}
|
||
]'>
|
||
<div class="flow-actors">
|
||
<div class="flow-actor" id="flow-actor-1"><span class="flow-actor-icon">🌐</span><span>api/*.py</span></div>
|
||
<div class="flow-actor" id="flow-actor-2"><span class="flow-actor-icon">🔑</span><span>deps 鉴权</span></div>
|
||
<div class="flow-actor" id="flow-actor-3"><span class="flow-actor-icon">📦</span><span>platform Service</span></div>
|
||
<div class="flow-actor" id="flow-actor-4"><span class="flow-actor-icon">🗄</span><span>core_ro</span></div>
|
||
</div>
|
||
<p class="flow-step-label">点击「下一步」看一层层往下走</p>
|
||
<div class="flow-controls">
|
||
<button class="btn flow-next-btn">下一步</button>
|
||
<button class="btn flow-reset-btn">重来</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="screen animate-in">
|
||
<div class="translation-block">
|
||
<div class="translation-code">
|
||
<span class="translation-label">平台客户 REST 薄路由(customers.py)</span>
|
||
<pre><code><span class="code-line"><span class="code-comment">"""代销平台 · 客户 L0 与资产读 API(canonical · 不要求 X-Agent-Type)。"""</span></span>
|
||
<span class="code-line">@router.get(<span class="code-string">"/{customer_id}/holdings"</span>)</span>
|
||
<span class="code-line"><span class="code-keyword">def</span> <span class="code-function">list_holdings</span>(</span>
|
||
<span class="code-line"> customer_id: str,</span>
|
||
<span class="code-line"> auth: AuthContext = Depends(get_platform_auth_context),</span>
|
||
<span class="code-line">):</span>
|
||
<span class="code-line"> assert_platform_customer_access(auth, customer_id, ...)</span>
|
||
<span class="code-line"> <span class="code-keyword">return</span> ok(platform_service.list_holdings(customer_id))</span></code></pre>
|
||
</div>
|
||
<div class="translation-english">
|
||
<span class="translation-label">白话</span>
|
||
<div class="translation-lines">
|
||
<p class="tl">文件头就写明:这是平台 canonical 读接口,不要 X-Agent-Type。</p>
|
||
<p class="tl">URL 用复数 customers + 嵌套 holdings,参数名和域 ID 一致。</p>
|
||
<p class="tl">鉴权用平台专用函数,不是 chat 那条 get_auth_context。</p>
|
||
<p class="tl">先断言「你有没有权看这个 customer_id」,再调 Service,路由里不出现 SQL。</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="quiz-container" id="quiz-platform-m1">
|
||
<div class="quiz-question-block"
|
||
data-correct="option-b"
|
||
data-explanation-right="对。v0.1 拍板:重复能力以本平台 API + platform Service 为准,Agent Tool 合并时改调同一层。"
|
||
data-explanation-wrong="不要在 Agent 里再抄一套查持仓 HTTP;会两套口径、联调必炸。">
|
||
<h3 class="quiz-question">理财师 Agent 也要查客户持仓,应该新建 /api/agent/holdings 吗?</h3>
|
||
<div class="quiz-options">
|
||
<button class="quiz-option" data-value="option-a" onclick="selectOption(this)">
|
||
<div class="quiz-option-radio"></div><span>要,Agent 专用路径更清晰</span>
|
||
</button>
|
||
<button class="quiz-option" data-value="option-b" onclick="selectOption(this)">
|
||
<div class="quiz-option-radio"></div><span>不要,改调 platform Service 或现有 /api/customers 路径</span>
|
||
</button>
|
||
<button class="quiz-option" data-value="option-c" onclick="selectOption(this)">
|
||
<div class="quiz-option-radio"></div><span>直接在 Agent 里写 SQL 更快</span>
|
||
</button>
|
||
</div>
|
||
<div class="quiz-feedback"></div>
|
||
</div>
|
||
<button class="quiz-check-btn" onclick="checkQuiz('quiz-platform-m1')">检查答案</button>
|
||
<button class="quiz-reset-btn" onclick="resetQuiz('quiz-platform-m1')">重做</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
<section class="module" id="module-2">
|
||
<div class="module-inner">
|
||
<p class="eyebrow animate-in">模块 2 · 平台鉴权</p>
|
||
<h1 class="module-title animate-in">get_platform_auth_context:<br>只要 JWT,不要 X-Agent-Type</h1>
|
||
<p class="module-lead animate-in">
|
||
对话线(<code>/api/chat</code>)必须带
|
||
<span class="term" data-definition="HTTP 请求头里的一行,声明这次走 customer/advisor/risk/analyst 哪条 Agent 业务线。">X-Agent-Type</span>;
|
||
平台读 API 故意<strong>不要</strong>——App 和仪表盘只证明「你是谁」,不声明「你在敲哪扇 Agent 窗」。
|
||
</p>
|
||
|
||
<div class="screen animate-in">
|
||
<h2>和 chat 鉴权的对照表</h2>
|
||
<table style="width:100%; border-collapse:collapse; margin: 1.5rem 0; font-size: 0.95rem;">
|
||
<thead><tr><th>场景</th><th>函数</th><th>X-Agent-Type</th><th>归属断言</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>平台 REST</td><td><code>get_platform_auth_context</code></td><td><strong>不要</strong></td><td><code>assert_platform_customer_access</code></td></tr>
|
||
<tr><td>对话 / 风控 REST</td><td><code>get_auth_context</code></td><td><strong>必须</strong></td><td><code>assert_customer_access</code> + 准入矩阵</td></tr>
|
||
</tbody>
|
||
</table>
|
||
<div class="callout callout-warning">
|
||
<strong>指挥 AI 接前端时:</strong> <code>apiFetch</code> 读持仓只带 Bearer;若误加 <code>X-Agent-Type: customer</code>,平台路由会忽略,但混进 chat 路由就会多一层校验甚至 401。
|
||
</div>
|
||
</div>
|
||
|
||
<div class="screen animate-in">
|
||
<h2>群聊:前端问「张三持仓多少」</h2>
|
||
<p>理财师工作台发 GET 持仓——只走平台三层,不进 LangGraph。</p>
|
||
|
||
<div class="chat-window" id="chat-platform-m2">
|
||
<div class="chat-messages">
|
||
<div class="chat-message" data-msg="0" data-sender="frontend" style="display:none">
|
||
<div class="chat-avatar" style="background: var(--color-accent)">🖥</div>
|
||
<div class="chat-bubble">
|
||
<span class="chat-sender" style="color: var(--color-accent)">前端</span>
|
||
<p>GET /api/customers/CUST-1001/holdings<br>Authorization: Bearer eyJ…<br><em>(无 X-Agent-Type)</em></p>
|
||
</div>
|
||
</div>
|
||
<div class="chat-message" data-msg="1" data-sender="api" style="display:none">
|
||
<div class="chat-avatar" style="background: #5A9DB8">🚪</div>
|
||
<div class="chat-bubble">
|
||
<span class="chat-sender" style="color: #5A9DB8">平台客户 REST 薄路由(customers.py)</span>
|
||
<p>Depends(get_platform_auth_context) → JWT 验签 OK<br>assert_platform_customer_access:STAFF-10086 名下有 CUST-1001 ✓</p>
|
||
</div>
|
||
</div>
|
||
<div class="chat-message" data-msg="2" data-sender="service" style="display:none">
|
||
<div class="chat-avatar" style="background: #E06B56">📦</div>
|
||
<div class="chat-bubble">
|
||
<span class="chat-sender" style="color: #E06B56">platform_service</span>
|
||
<p>list_holdings(customer_id) → prepare_row 脱敏开关检查 → 分页组装 items</p>
|
||
</div>
|
||
</div>
|
||
<div class="chat-message" data-msg="3" data-sender="core" style="display:none">
|
||
<div class="chat-avatar" style="background: #2D8B55">🗄</div>
|
||
<div class="chat-bubble">
|
||
<span class="chat-sender" style="color: #2D8B55">core_ro</span>
|
||
<p>SELECT … FROM holdings WHERE customer_id = ?<br>只读,不写 Core 正式账</p>
|
||
</div>
|
||
</div>
|
||
<div class="chat-message" data-msg="4" data-sender="frontend" style="display:none">
|
||
<div class="chat-avatar" style="background: var(--color-accent)">🖥</div>
|
||
<div class="chat-bubble">
|
||
<span class="chat-sender" style="color: var(--color-accent)">前端</span>
|
||
<p>收到 200 + data.items,渲染持仓表。全程没经过 chat.py。</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="chat-typing" style="display:none">
|
||
<div class="chat-avatar" id="chat-platform-m2-typing-avatar">?</div>
|
||
<div class="chat-typing-dots">
|
||
<span class="typing-dot"></span><span class="typing-dot"></span><span class="typing-dot"></span>
|
||
</div>
|
||
</div>
|
||
<div class="chat-controls">
|
||
<button class="btn chat-next-btn">下一条</button>
|
||
<button class="btn chat-all-btn">自动播放</button>
|
||
<button class="btn chat-reset-btn">重来</button>
|
||
<span class="chat-progress"></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="screen animate-in">
|
||
<div class="translation-block">
|
||
<div class="translation-code">
|
||
<span class="translation-label">deps.py</span>
|
||
<pre><code><span class="code-line"><span class="code-keyword">def</span> <span class="code-function">get_platform_auth_context</span>(request: Request) -> AuthContext:</span>
|
||
<span class="code-line"> <span class="code-string">"""平台 API 鉴权:JWT 通道 **不要求** X-Agent-Type。"""</span></span>
|
||
<span class="code-line"> auth_header = request.headers.get(<span class="code-string">"Authorization"</span>, <span class="code-string">""</span>)</span>
|
||
<span class="code-line"> <span class="code-keyword">if</span> auth_header[:<span class="code-number">7</span>].lower() == <span class="code-string">"bearer "</span>:</span>
|
||
<span class="code-line"> claims = verify_token(token)</span>
|
||
<span class="code-line"> <span class="code-keyword">return</span> _bind_state(request, _claims_to_auth(claims))</span>
|
||
<span class="code-line"> <span class="code-comment"># dev:X-Debug-Role / X-Debug-Actor 兜底</span></span></code></pre>
|
||
</div>
|
||
<div class="translation-english">
|
||
<span class="translation-label">白话</span>
|
||
<div class="translation-lines">
|
||
<p class="tl">函数名就写死:平台鉴权,不读 X-Agent-Type。</p>
|
||
<p class="tl">有 Bearer 就验 JWT,解析出 actor_id 和 roles。</p>
|
||
<p class="tl">本地开发没 JWT 时,可用 debug 头冒充身份(仅 development)。</p>
|
||
<p class="tl">和 get_auth_context 不同:后者在 JWT 通道会强制校验 X-Agent-Type + 准入矩阵。</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="quiz-container" id="quiz-platform-m2">
|
||
<div class="quiz-question-block"
|
||
data-correct="option-a"
|
||
data-explanation-right="问数 POST /api/analyst/chat 走 get_platform_auth_context,前端 analyst.ts 只带 Bearer。"
|
||
data-explanation-wrong="analyst 问数是平台读能力,不是 /api/chat 对话分流,不要 X-Agent-Type。">
|
||
<h3 class="quiz-question">分析员「问数工作台」调 /api/analyst/chat,要带 X-Agent-Type: analyst 吗?</h3>
|
||
<div class="quiz-options">
|
||
<button class="quiz-option" data-value="option-a" onclick="selectOption(this)">
|
||
<div class="quiz-option-radio"></div><span>不要,走平台鉴权</span>
|
||
</button>
|
||
<button class="quiz-option" data-value="option-b" onclick="selectOption(this)">
|
||
<div class="quiz-option-radio"></div><span>要,和 ChatPanel 一样</span>
|
||
</button>
|
||
</div>
|
||
<div class="quiz-feedback"></div>
|
||
</div>
|
||
<button class="quiz-check-btn" onclick="checkQuiz('quiz-platform-m2')">检查答案</button>
|
||
<button class="quiz-reset-btn" onclick="resetQuiz('quiz-platform-m2')">重做</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
<section class="module" id="module-3">
|
||
<div class="module-inner">
|
||
<p class="eyebrow animate-in">模块 3 · 出参脱敏</p>
|
||
<h1 class="module-title animate-in">PLATFORM_RESPONSE_DESENSITIZE:<br>模拟库原样,真库再遮</h1>
|
||
<p class="module-lead animate-in">
|
||
环境变量
|
||
<code>PLATFORM_RESPONSE_DESENSITIZE</code>
|
||
控制平台 API 出参是否打码姓名/手机/证件。
|
||
v0.1 默认<strong>关</strong>——本地 Core 模拟库联调看原值;接真 Core 再开,在
|
||
<span class="term" data-definition="Service 层 = 业务组装层,REST 和日后 Agent Tool 共用,避免两处各写一套脱敏逻辑。">Platform Service</span>
|
||
统一出口处理。
|
||
</p>
|
||
|
||
<div class="screen animate-in">
|
||
<h2>开关行为一览</h2>
|
||
<table style="width:100%; border-collapse:collapse; margin: 1.5rem 0; font-size: 0.95rem;">
|
||
<thead><tr><th>开关</th><th>模拟库联调</th><th>生产 / 真 Core</th></tr></thead>
|
||
<tbody>
|
||
<tr><td><code>false</code>(默认)</td><td>L0 字段原样返回</td><td>—</td></tr>
|
||
<tr><td><code>true</code></td><td>—</td><td>姓名/手机/证件/银行卡打码;<code>customer_id</code> 不脱敏</td></tr>
|
||
</tbody>
|
||
</table>
|
||
<div class="callout callout-accent">
|
||
<strong>实现位置:</strong> 响应脱敏(<code>app/service/platform/common.py</code>)的 <code>maybe_desensitize_row</code> → <code>prepare_row</code>。REST 与 Agent 适配层都应走这里,不要在路由里手写 mask。
|
||
</div>
|
||
</div>
|
||
|
||
<div class="screen animate-in">
|
||
<div class="translation-block">
|
||
<div class="translation-code">
|
||
<span class="translation-label">响应脱敏(platform/common.py)</span>
|
||
<pre><code><span class="code-line"><span class="code-keyword">def</span> <span class="code-function">maybe_desensitize_row</span>(row: dict | None) -> dict | None:</span>
|
||
<span class="code-line"> <span class="code-keyword">if</span> row <span class="code-keyword">is</span> None <span class="code-keyword">or not</span> settings.platform_response_desensitize:</span>
|
||
<span class="code-line"> <span class="code-keyword">return</span> row</span>
|
||
<span class="code-line"> <span class="code-keyword">for</span> key, masker <span class="code-keyword">in</span> (</span>
|
||
<span class="code-line"> (<span class="code-string">"display_name"</span>, d.mask_name),</span>
|
||
<span class="code-line"> (<span class="code-string">"mobile_phone"</span>, d.mask_phone),</span>
|
||
<span class="code-line"> (<span class="code-string">"id_card_no"</span>, d.mask_id_card),</span>
|
||
<span class="code-line"> ...</span>
|
||
<span class="code-line"> ): ...</span>
|
||
<span class="code-line"><span class="code-keyword">def</span> <span class="code-function">prepare_row</span>(row):</span>
|
||
<span class="code-line"> <span class="code-keyword">return</span> to_jsonable(maybe_desensitize_row(row) or row)</span></code></pre>
|
||
</div>
|
||
<div class="translation-english">
|
||
<span class="translation-label">白话</span>
|
||
<div class="translation-lines">
|
||
<p class="tl">开关关着 → 数据库行原样返回,方便本地对账。</p>
|
||
<p class="tl">开关开着 → 按字段名找 mask 函数,逐个打码。</p>
|
||
<p class="tl">prepare_row 是统一出口:先脱敏,再把 Decimal/日期转成 JSON 能序列化的类型。</p>
|
||
<p class="tl">指挥 AI 加新字段时,若含 PII,记得在这里登记 masker。</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="screen animate-in">
|
||
<h2>自检:你能否指挥 AI 正确切换?</h2>
|
||
<div class="quiz-container" id="quiz-platform-m3">
|
||
<div class="quiz-question-block"
|
||
data-correct="option-c"
|
||
data-explanation-right="对。默认 false 是为了模拟库联调;上真库时在 .env 设 PLATFORM_RESPONSE_DESENSITIZE=true,脱敏在 Service 层自动生效。"
|
||
data-explanation-wrong="不要在每个路由里 copy 打码逻辑;开关在 settings,行为在 响应脱敏(platform/common.py)。">
|
||
<h3 class="quiz-question">准备接真 Core,要让出参打码手机号,最省事的做法是?</h3>
|
||
<div class="quiz-options">
|
||
<button class="quiz-option" data-value="option-a" onclick="selectOption(this)">
|
||
<div class="quiz-option-radio"></div><span>在平台客户 REST 薄路由(customers.py)返回前手写 replace</span>
|
||
</button>
|
||
<button class="quiz-option" data-value="option-b" onclick="selectOption(this)">
|
||
<div class="quiz-option-radio"></div><span>让前端自己 mask 手机号</span>
|
||
</button>
|
||
<button class="quiz-option" data-value="option-c" onclick="selectOption(this)">
|
||
<div class="quiz-option-radio"></div><span>.env 设 PLATFORM_RESPONSE_DESENSITIZE=true</span>
|
||
</button>
|
||
</div>
|
||
<div class="quiz-feedback"></div>
|
||
</div>
|
||
<div class="quiz-question-block"
|
||
data-correct="option-b"
|
||
data-explanation-right="customer_id 是业务主键,联调和归属校验都要用,契约明确不脱敏。"
|
||
data-explanation-wrong="customer_id 不在 mask 列表里,脱敏只覆盖姓名/手机/证件/银行卡等 PII 字段。">
|
||
<h3 class="quiz-question">脱敏开启后,customer_id 会被打码吗?</h3>
|
||
<div class="quiz-options">
|
||
<button class="quiz-option" data-value="option-a" onclick="selectOption(this)">
|
||
<div class="quiz-option-radio"></div><span>会,全部敏感字段都遮</span>
|
||
</button>
|
||
<button class="quiz-option" data-value="option-b" onclick="selectOption(this)">
|
||
<div class="quiz-option-radio"></div><span>不会,customer_id 保留原值</span>
|
||
</button>
|
||
</div>
|
||
<div class="quiz-feedback"></div>
|
||
</div>
|
||
<button class="quiz-check-btn" onclick="checkQuiz('quiz-platform-m3')">检查答案</button>
|
||
<button class="quiz-reset-btn" onclick="resetQuiz('quiz-platform-m3')">重做</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
<section class="module" id="module-4">
|
||
<div class="module-inner">
|
||
<p class="eyebrow animate-in">模块 4 · 接缝与草案</p>
|
||
<h1 class="module-title animate-in">适当性只准一条 canonical<br>还有空壳与行情草案</h1>
|
||
<p class="module-lead animate-in">
|
||
平台 API 是 v0.1 的「对外标准」,但仓库里已有<strong>重复能力、空壳路由、Phase B 草案</strong>。
|
||
指挥 AI 加第三条适当性 URL 或前端直连行情源,都会踩接缝。
|
||
</p>
|
||
|
||
<div class="screen animate-in">
|
||
<h2>路由补全:staff 与空壳</h2>
|
||
<div class="pattern-cards">
|
||
<div class="pattern-card">
|
||
<h3>已实现 · v0.1 四域 + staff</h3>
|
||
<p><code>/api/customers</code> · <code>products</code> · <code>advisors</code> · <code>compliance</code> · <code>staff</code></p>
|
||
</div>
|
||
<div class="pattern-card">
|
||
<h3>空壳 · 一期不做</h3>
|
||
<p>知识库空壳 API(<code>knowledge.py</code>) · 管理空壳 API(<code>admin.py</code>) — T-21 拍板:脚本入库,无上传/重建 HTTP。</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="screen animate-in">
|
||
<h2>适当性:两条 URL,别再开第三条</h2>
|
||
<div class="translation-block">
|
||
<div class="translation-code">
|
||
<span class="translation-label">canonical(平台)</span>
|
||
<pre><code><span class="code-line">POST /api/compliance/suitability-check</span>
|
||
<span class="code-line">→ get_platform_auth_context</span>
|
||
<span class="code-line">→ app/service/platform/</span></code></pre>
|
||
</div>
|
||
<div class="translation-english">
|
||
<span class="translation-label">风控专用(对话/演示)</span>
|
||
<div class="translation-lines">
|
||
<p class="tl"><code>POST /api/risk/suitability/check</code> — 风控 REST + X-Agent-Type: risk。</p>
|
||
<p class="tl">客服 <code>suitability_check</code> Tool 走 <code>core_ro.check_suitability</code>,是第三条<strong>对话内</strong>路径,但底层同一判定内核。</p>
|
||
<p class="tl">新增 HTTP 路由前先查契约迁移表,避免四个入口四个口径。</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="screen animate-in">
|
||
<h2>行情 Phase B(草案 · 未落地)</h2>
|
||
<p>v0.2 草案:<code>nav-snapshot</code> · <code>sync_market_nav</code>。C-05 硬规则:<strong>禁止爬虫、禁止前端直连第三方行情</strong>——净值须走后端同步管道。</p>
|
||
|
||
<div class="quiz-container" id="quiz-platform-m4">
|
||
<div class="quiz-question-block"
|
||
data-correct="option-c"
|
||
data-explanation-right="对。PLATFORM_RESPONSE_DESENSITIZE 默认关是为了联调看原值;上真 Core 再开,customer_id 仍不打码。"
|
||
data-explanation-wrong="脱敏开关是环境策略,不是「忘了做脱敏」。">
|
||
<h3 class="quiz-question">联调时响应是明文 ID,AI 说「脱敏坏了」,你怎么回?</h3>
|
||
<div class="quiz-options">
|
||
<button class="quiz-option" data-value="option-a" onclick="selectOption(this)">
|
||
<div class="quiz-option-radio"></div><span>是 bug,强制默认开脱敏</span>
|
||
</button>
|
||
<button class="quiz-option" data-value="option-b" onclick="selectOption(this)">
|
||
<div class="quiz-option-radio"></div><span>让前端自己 mask</span>
|
||
</button>
|
||
<button class="quiz-option" data-value="option-c" onclick="selectOption(this)">
|
||
<div class="quiz-option-radio"></div><span>模拟库默认关是拍板;真 Core 再开 PLATFORM_RESPONSE_DESENSITIZE</span>
|
||
</button>
|
||
</div>
|
||
<div class="quiz-feedback"></div>
|
||
</div>
|
||
<button class="quiz-check-btn" onclick="checkQuiz('quiz-platform-m4')">检查答案</button>
|
||
<button class="quiz-reset-btn" onclick="resetQuiz('quiz-platform-m4')">重做</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</main>
|
||
|
||
</body>
|
||
</html>
|