2026-09-09 23:22:59 +08:00
|
|
|
|
<section class="module" id="module-1">
|
|
|
|
|
|
<div class="module-inner">
|
|
|
|
|
|
<p class="eyebrow animate-in">模块 1 · 风控能力</p>
|
|
|
|
|
|
<h1 class="module-title animate-in">预警、适当性、AML<br>与模拟交易</h1>
|
|
|
|
|
|
<p class="module-lead animate-in">
|
|
|
|
|
|
风控专员 Demo <code>STAFF-30001</code> 带 <code>risk_officer</code> + <code>risk_demo</code> 角色:
|
|
|
|
|
|
能看预警台账、处置、跑 AML 扫描,还能在模拟交易页触发规则引擎。
|
|
|
|
|
|
本地登录页一键切换,改 JWT 角色后需<strong>重新登录</strong>。
|
2026-09-11 17:07:22 +08:00
|
|
|
|
仓库基线 <code>merger</code> · <code>python -m pytest</code> → <strong>825 passed</strong>。
|
2026-09-09 23:22:59 +08:00
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="screen animate-in">
|
|
|
|
|
|
<h2>四条能力线(REST + 对话 Tool 共用底座)</h2>
|
|
|
|
|
|
<div class="pattern-cards">
|
|
|
|
|
|
<div class="pattern-card">
|
|
|
|
|
|
<h3>预警台账 FR-4</h3>
|
|
|
|
|
|
<p><code>GET /api/risk/alerts</code> 分页查询;<code>POST .../handle</code> 人工处置(仅 risk_officer)。响应固定带 disclaimer。</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="pattern-card">
|
2026-09-10 14:32:25 +08:00
|
|
|
|
<h3>适当性校验 R-02 / FR-2</h3>
|
|
|
|
|
|
<p><code>POST /api/risk/suitability/check</code> 与网关共用 <span class="term" data-definition="Core 只读查询层(core_ro.py):从 jinrong_core 模拟库读 L0/持仓/流水,并做 C×R 适当性矩阵判定。">Core 只读层</span> 的 <code>check_suitability</code>(旧 SUIT-001~008 已退役)。客户本人 / 理财师名下 / 风控全量。</p>
|
2026-09-09 23:22:59 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="pattern-card">
|
|
|
|
|
|
<h3>AML 扫描 FR-5</h3>
|
|
|
|
|
|
<p><code>POST /api/risk/aml/scan</code>:仅 risk_officer 触发全量扫描,生成 aml 类预警。</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="pattern-card">
|
|
|
|
|
|
<h3>模拟交易 FR-1</h3>
|
2026-09-10 10:57:40 +08:00
|
|
|
|
<p><code>POST /api/simulate/trade</code>:risk_demo 或客户本人可提交;走 模拟写 Core 网关(trade_gateway.py) → 适当性 → 规则引擎(risk_engine.py)。</p>
|
2026-09-09 23:22:59 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="callout callout-accent">
|
|
|
|
|
|
<strong>鉴权与问数不同:</strong> 风控 REST 走 <code>get_auth_context</code>,前端 <code>risk.ts</code> 必须带 <code>X-Agent-Type: risk</code> + Bearer。缺头会 401 <code>AUTH_401_MISSING_AGENT_TYPE</code>。
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="screen animate-in">
|
|
|
|
|
|
<h2>群聊:风控专员打开预警页</h2>
|
|
|
|
|
|
<p>浏览器通过 riskHeaders 声明自己在敲风控窗:</p>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="chat-window" id="chat-risk-m1">
|
|
|
|
|
|
<div class="chat-messages">
|
|
|
|
|
|
<div class="chat-message" data-msg="0" data-sender="browser" style="display:none">
|
|
|
|
|
|
<div class="chat-avatar" style="background:#D94F30">前</div>
|
|
|
|
|
|
<div class="chat-bubble">
|
|
|
|
|
|
<span class="chat-sender" style="color:#D94F30">RiskAlertsPage</span>
|
|
|
|
|
|
<p>GET /api/risk/alerts · Bearer + X-Agent-Type: risk</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="chat-message" data-msg="1" data-sender="api" style="display:none">
|
|
|
|
|
|
<div class="chat-avatar" style="background:#E87A62">API</div>
|
|
|
|
|
|
<div class="chat-bubble">
|
|
|
|
|
|
<span class="chat-sender" style="color:#E87A62">deps.get_auth_context</span>
|
|
|
|
|
|
<p>验 JWT + 矩阵:STAFF-30001 risk_officer 放行</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="chat-message" data-msg="2" data-sender="repo" style="display:none">
|
|
|
|
|
|
<div class="chat-avatar" style="background:#B83E24">库</div>
|
|
|
|
|
|
<div class="chat-bubble">
|
|
|
|
|
|
<span class="chat-sender" style="color:#B83E24">RiskRepository</span>
|
|
|
|
|
|
<p>list_alerts(status=pending_review) → items + total + disclaimer</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="chat-message" data-msg="3" data-sender="ui" style="display:none">
|
|
|
|
|
|
<div class="chat-avatar" style="background:#D4A843">页</div>
|
|
|
|
|
|
<div class="chat-bubble">
|
|
|
|
|
|
<span class="chat-sender" style="color:#D4A843">Table + Modal</span>
|
|
|
|
|
|
<p>展示预警列表;点处置调 handleAlert → POST .../handle</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="chat-typing" id="chat-risk-m1-typing" style="display:none">
|
|
|
|
|
|
<div class="chat-avatar" id="chat-risk-m1-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">
|
2026-09-10 10:57:40 +08:00
|
|
|
|
<span class="translation-label">CODE · 模拟交易入口(simulate.py)鉴权</span>
|
2026-09-09 23:22:59 +08:00
|
|
|
|
<pre><code><span class="code-line"><span class="code-keyword">if not</span> (auth.has_role(<span class="code-string">"risk_demo"</span>)</span>
|
|
|
|
|
|
<span class="code-line"> <span class="code-keyword">or</span> (auth.is_customer()</span>
|
|
|
|
|
|
<span class="code-line"> <span class="code-keyword">and</span> auth.customer_id == req.customer_id)):</span>
|
|
|
|
|
|
<span class="code-line"> deny(auth, <span class="code-string">"AUTH_403_ROLE"</span>, ...)</span>
|
|
|
|
|
|
<span class="code-line"><span class="code-keyword">return</span> submit_trade(req.model_dump(), actor_id=auth.actor_id)</span></code></pre>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="translation-english">
|
|
|
|
|
|
<span class="translation-label">白话</span>
|
|
|
|
|
|
<div class="translation-lines">
|
|
|
|
|
|
<p class="tl">模拟交易不是谁都能点:要么 risk_demo 演示号,要么客户给自己下单。</p>
|
|
|
|
|
|
<p class="tl">STAFF-30001 有 risk_demo 权限,可在 RiskSimulatePage 代客触发规则。</p>
|
|
|
|
|
|
<p class="tl">越权会 deny 并写审计,不会悄悄放行。</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="quiz-container" id="quiz-risk-m1">
|
|
|
|
|
|
<div class="quiz-question-block"
|
|
|
|
|
|
data-correct="option-b"
|
|
|
|
|
|
data-explanation-right="risk_demo 是 STAFF-30001 的演示权限,允许在模拟交易页提交 trade 请求。"
|
|
|
|
|
|
data-explanation-wrong="普通 staff 没有 risk_demo;模拟交易专门给演示账号或客户本人。">
|
|
|
|
|
|
<h3 class="quiz-question">STAFF-30001 能在模拟交易页提交申购吗?</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>能,因为有 risk_demo 角色</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button class="quiz-option" data-value="option-c" onclick="selectOption(this)">
|
|
|
|
|
|
<div class="quiz-option-radio"></div><span>能,任何 staff 都行</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="quiz-feedback"></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<button class="quiz-check-btn" onclick="checkQuiz('quiz-risk-m1')">检查答案</button>
|
|
|
|
|
|
<button class="quiz-reset-btn" onclick="resetQuiz('quiz-risk-m1')">重做</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|