feat(threshold): Implement customer loss threshold configuration and notification system

- 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.
This commit is contained in:
2026-09-10 10:57:40 +08:00
parent 6f222f1c56
commit 4b8e11c9bd
102 changed files with 4054 additions and 445 deletions
@@ -3,8 +3,8 @@
<p class="eyebrow animate-in">模块 2 · 模拟交易链路</p>
<h1 class="module-title animate-in">一笔演示交易<br>怎么长出预警单?</h1>
<p class="module-lead animate-in">
<code>RiskSimulatePage</code> 提交表单 → <code>simulate.py</code> 验权 → <code>trade_gateway</code> 写库 →
<code>risk_engine</code> 扫规则 → 聚合进 <code>risk_alert</code>。
<code>RiskSimulatePage</code> 提交表单 → 模拟交易入口(<code>simulate.py</code>)验权 → 模拟写 Core 网关(<code>trade_gateway.py</code>)写库 →
规则引擎(<code>risk_engine.py</code>)扫规则 → 聚合进 <code>risk_alert</code>。
像演示用的「假收银台」:钱是假的,但风控流程是真的。
</p>
@@ -12,16 +12,16 @@
<h2>四段链路(动画)</h2>
<div class="flow-animation" data-steps='[
{"highlight":"flow-actor-1","label":"RiskSimulatePage:submitSimulateTrade(token, \"risk\", body)"},
{"highlight":"flow-actor-2","label":"POST /api/simulate/trade → 鉴权 risk_demo 或客户本人","packet":true,"from":"1","to":"2"},
{"highlight":"flow-actor-3","label":"trade_gateway:R-02 适当性 → INSERT core_trade(唯一写 Core 路径)","packet":true,"from":"2","to":"3"},
{"highlight":"flow-actor-4","label":"risk_engine 扫描 → 写 risk_alert(大额/集中度等)","packet":true,"from":"3","to":"4"},
{"highlight":"flow-actor-2","label":"模拟交易入口(simulate.py):POST /api/simulate/trade → 鉴权 risk_demo 或客户本人","packet":true,"from":"1","to":"2"},
{"highlight":"flow-actor-3","label":"模拟写 Core 网关(trade_gateway.py):R-02 适当性 → INSERT core_trade(唯一写 Core 路径)","packet":true,"from":"2","to":"3"},
{"highlight":"flow-actor-4","label":"规则引擎(risk_engine.py)扫描 → 写 risk_alert(大额/集中度等)","packet":true,"from":"3","to":"4"},
{"highlight":"flow-actor-1","label":"台账 GET /api/risk/alerts 可见 + 固定 disclaimer","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>模拟交易 UI</span></div>
<div class="flow-actor" id="flow-actor-2"><span class="flow-actor-icon">🚪</span><span>simulate.py</span></div>
<div class="flow-actor" id="flow-actor-3"><span class="flow-actor-icon">⚙</span><span>trade_gateway</span></div>
<div class="flow-actor" id="flow-actor-4"><span class="flow-actor-icon">🛡</span><span>risk_engine</span></div>
<div class="flow-actor" id="flow-actor-2"><span class="flow-actor-icon">🚪</span><span>模拟交易入口</span></div>
<div class="flow-actor" id="flow-actor-3"><span class="flow-actor-icon">⚙</span><span>模拟写 Core 网关</span></div>
<div class="flow-actor" id="flow-actor-4"><span class="flow-actor-icon">🛡</span><span>规则引擎</span></div>
</div>
<p class="flow-step-label">Preset A-3:CUST-3001 · 50 万 → 大额预警 · Preset A-1:适当性阻断</p>
<div class="flow-controls">
@@ -34,12 +34,12 @@
<div class="screen animate-in">
<h2>代码地图</h2>
<div class="file-tree animate-in">
<div class="tree-item tree-folder">app/api/simulate.py</div>
<div class="tree-item tree-folder">模拟交易入口(app/api/simulate.py)</div>
<div class="tree-item tree-indent">薄路由:鉴权 + 调 submit_trade</div>
<div class="tree-item tree-folder">app/gateway/trade_gateway.py</div>
<div class="tree-item tree-folder">模拟写 Core 网关(app/gateway/trade_gateway.py)</div>
<div class="tree-item tree-indent">适当性 → 写 core_trade → 调 risk 引擎</div>
<div class="tree-item tree-folder">app/service/risk/</div>
<div class="tree-item tree-indent">risk_engine.py · alert_service.py · 规则聚合</div>
<div class="tree-item tree-indent">规则引擎(risk_engine.py) · alert_service.py · 规则聚合</div>
<div class="tree-item tree-folder">web/src/pages/risk/RiskSimulatePage.tsx</div>
<div class="tree-item tree-indent">预设 A-1 / A-3 一键填表</div>
</div>
@@ -48,7 +48,7 @@
<div class="screen animate-in">
<div class="translation-block">
<div class="translation-code">
<span class="translation-label">CODE · simulate.py</span>
<span class="translation-label">CODE · 模拟交易入口(simulate.py)</span>
<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 class="code-keyword">and</span></span>
<span class="code-line"> auth.customer_id == req.customer_id)):</span>
@@ -67,18 +67,18 @@
<div class="quiz-container" id="quiz-risk-m2">
<div class="quiz-question-block"
data-correct="option-c"
data-explanation-right="trade_gateway 是唯一允许向 jinrong_core 写 INSERT 的路径(模拟成交);Agent 画像不能直接改 Core 正式字段。"
data-explanation-right="模拟写 Core 网关(trade_gateway.py)是唯一允许向 jinrong_core 写 INSERT 的路径(模拟成交);Agent 画像不能直接改 Core 正式字段。"
data-explanation-wrong="问数和其他 Agent 对 Core 是只读;只有网关模拟交易可写 core_trade。">
<h3 class="quiz-question">Demo 里谁可以把新成交写进 core_trade?</h3>
<div class="quiz-options">
<button class="quiz-option" data-value="option-a" onclick="selectOption(this)">
<div class="quiz-option-radio"></div><span>analyst_agent 问数 SQL</span>
<div class="quiz-option-radio"></div><span>NL→SQL 编排(analyst_agent.py)问数 SQL</span>
</button>
<button class="quiz-option" data-value="option-b" onclick="selectOption(this)">
<div class="quiz-option-radio"></div><span>风控对话 Tool</span>
</button>
<button class="quiz-option" data-value="option-c" onclick="selectOption(this)">
<div class="quiz-option-radio"></div><span>trade_gateway(经 simulate/trade)</span>
<div class="quiz-option-radio"></div><span>模拟写 Core 网关(trade_gateway.py,经 simulate/trade)</span>
</button>
</div>
<div class="quiz-feedback"></div>