- Introduced `build_all.py` script to automate the assembly of course modules into a single `index.html` file. - Created `index.html` for the main course overview, featuring a structured layout and navigation for various modules. - Developed `_base.html` and `_footer.html` templates for the advisor module, ensuring consistent styling and structure. - Added `build.sh` script for individual module assembly, enhancing modularity and ease of updates. - Implemented multiple module HTML files detailing specific training scenarios and functionalities for advisors, including interactive elements and quizzes. This update significantly enhances the course delivery framework, providing a comprehensive and interactive learning experience for advisors.
398 lines
22 KiB
HTML
398 lines
22 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>风控监测 Agent 深潜 · 交互导览</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: #D94F30;
|
||
--color-accent-hover: #B83E24;
|
||
--color-accent-light: #FCEEEA;
|
||
--color-accent-muted: #E87A62;
|
||
}
|
||
</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">风控监测 Agent 深潜</span>
|
||
<div class="nav-dots" id="nav-dots" role="tablist">
|
||
<button class="nav-dot" data-target="module-1" data-tooltip="四条能力线" role="tab" aria-label="模块 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>
|
||
</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">预警、适当性、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>。
|
||
</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">
|
||
<h3>适当性校验 FR-2</h3>
|
||
<p><code>POST /api/risk/suitability/check</code>:客户仅本人、理财师名下、风控全量。每次直调落 audit。</p>
|
||
</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>
|
||
<p><code>POST /api/simulate/trade</code>:risk_demo 或客户本人可提交;走 trade_gateway → 适当性 → 规则引擎。</p>
|
||
</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">
|
||
<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>
|
||
<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>
|
||
<section class="module" id="module-2">
|
||
<div class="module-inner">
|
||
<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>。
|
||
像演示用的「假收银台」:钱是假的,但风控流程是真的。
|
||
</p>
|
||
|
||
<div class="screen animate-in">
|
||
<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-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>
|
||
<p class="flow-step-label">Preset A-3:CUST-3001 · 50 万 → 大额预警 · Preset A-1:适当性阻断</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">
|
||
<h2>代码地图</h2>
|
||
<div class="file-tree animate-in">
|
||
<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-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-folder">web/src/pages/risk/RiskSimulatePage.tsx</div>
|
||
<div class="tree-item tree-indent">预设 A-1 / A-3 一键填表</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="screen animate-in">
|
||
<div class="translation-block">
|
||
<div class="translation-code">
|
||
<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>
|
||
<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(), ...)</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 在 jwt_service 里配了 risk_demo,所以能造 A-3 大额演示数据。</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<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-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>
|
||
</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>
|
||
</button>
|
||
</div>
|
||
<div class="quiz-feedback"></div>
|
||
</div>
|
||
<button class="quiz-check-btn" onclick="checkQuiz('quiz-risk-m2')">检查答案</button>
|
||
<button class="quiz-reset-btn" onclick="resetQuiz('quiz-risk-m2')">重做</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
<section class="module" id="module-3">
|
||
<div class="module-inner">
|
||
<p class="eyebrow animate-in">模块 3 · REST 与对话 Tool</p>
|
||
<h1 class="module-title animate-in">台账 REST 要带 risk 头<br>对话靠五只 Tool</h1>
|
||
<p class="module-lead animate-in">
|
||
风控<span class="term" data-definition="REST:用 URL + HTTP 方法(GET/POST)访问的后端接口,浏览器和脚本都能调。">REST</span>
|
||
走 <code>get_auth_context</code>,前端必须带 <code>X-Agent-Type: risk</code>。
|
||
风控对话则通过 <code>chat_tools</code> 注册五只只读 Tool + <code>query_agent_behavior</code>,由 <code>tool_service</code> 统一分发。
|
||
</p>
|
||
|
||
<div class="screen animate-in">
|
||
<h2>预警 REST 两端点</h2>
|
||
<div class="pattern-cards">
|
||
<div class="pattern-card">
|
||
<h3>GET /api/risk/alerts</h3>
|
||
<p>risk_officer / risk_manager 全量;compliance 强制只看 aml;响应含 <code>disclaimer</code></p>
|
||
</div>
|
||
<div class="pattern-card">
|
||
<h3>POST /api/risk/alerts/{id}/handle</h3>
|
||
<p>仅 risk_officer;handler_result 三枚举 + 审计;409 状态冲突</p>
|
||
</div>
|
||
</div>
|
||
<div class="callout callout-warning">
|
||
<strong>曾踩过的坑:</strong> <code>web/src/api/risk.ts</code> 若漏 <code>X-Agent-Type: risk</code>,JWT 正确也 401。
|
||
问数 API 则相反——不要带这个头。
|
||
</div>
|
||
</div>
|
||
|
||
<div class="screen animate-in">
|
||
<h2>chat_tools 注册表(C1 + 扩展)</h2>
|
||
<div class="file-tree animate-in">
|
||
<div class="tree-item tree-folder">app/service/risk/chat_tools.py</div>
|
||
<div class="tree-item tree-indent">alert_query — 客户或全量待审预警</div>
|
||
<div class="tree-item tree-indent">customer_context — L0 + L3 + 待审预警</div>
|
||
<div class="tree-item tree-indent">suitability_check — 只读校验 + 审计落库</div>
|
||
<div class="tree-item tree-indent">aml_lookup — 名单匹配</div>
|
||
<div class="tree-item tree-indent">query_overdue_alerts — 超期未处置(FR-9)</div>
|
||
<div class="tree-item tree-indent">query_agent_behavior — 代理人行为链(FR-10)</div>
|
||
</div>
|
||
<p>红线:Tool 只读,不生成预警单、不处置、不改正式风险等级。</p>
|
||
</div>
|
||
|
||
<div class="screen animate-in">
|
||
<h2>群聊:风控问「今天多少待审预警」</h2>
|
||
<div class="chat-window" id="chat-risk-m3">
|
||
<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">RiskChatPage</span>
|
||
<p>POST /api/chat/stream · X-Agent-Type: risk</p>
|
||
</div>
|
||
</div>
|
||
<div class="chat-message" data-msg="1" data-sender="chat" style="display:none">
|
||
<div class="chat-avatar" style="background:#E87A62">API</div>
|
||
<div class="chat-bubble">
|
||
<span class="chat-sender" style="color:#E87A62">tool_service</span>
|
||
<p>意图命中「待审预警」→ alert_query(customer_id 空缺=全量)</p>
|
||
</div>
|
||
</div>
|
||
<div class="chat-message" data-msg="2" data-sender="tool" style="display:none">
|
||
<div class="chat-avatar" style="background:#B83D24">Tool</div>
|
||
<div class="chat-bubble">
|
||
<span class="chat-sender" style="color:#B83D24">alert_query</span>
|
||
<p>RiskRepository.list_alerts → 今日新增口径 + alert_id 列表</p>
|
||
</div>
|
||
</div>
|
||
<div class="chat-message" data-msg="3" data-sender="llm" style="display:none">
|
||
<div class="chat-avatar" style="background:#D4A843">AI</div>
|
||
<div class="chat-bubble">
|
||
<span class="chat-sender" style="color:#D4A843">DeepSeek</span>
|
||
<p>组织自然语言;可追问 query_agent_behavior 查 STAFF 行为链</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="chat-typing" id="chat-risk-m3-typing" style="display:none">
|
||
<div class="chat-avatar" id="chat-risk-m3-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">前端 · risk.ts + simulate</span>
|
||
<pre><code><span class="code-line">apiFetch(<span class="code-string">'/api/risk/alerts'</span>, {</span>
|
||
<span class="code-line"> token, agentType: <span class="code-string">'risk'</span>, <span class="code-comment">// 必须</span></span>
|
||
<span class="code-line">})</span>
|
||
<span class="code-line"><span class="code-comment">// RiskSimulatePage → /api/simulate/trade 同样带 risk</span></span></code></pre>
|
||
</div>
|
||
<div class="translation-english">
|
||
<span class="translation-label">白话</span>
|
||
<div class="translation-lines">
|
||
<p class="tl">台账页、模拟交易页、风控对话——凡走 get_auth_context 的都要声明 risk 线。</p>
|
||
<p class="tl">处置预警用 handle REST;日常问数用对话 Tool,两者不要混接口。</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="quiz-container" id="quiz-risk-m3">
|
||
<div class="quiz-question-block"
|
||
data-correct="option-a"
|
||
data-explanation-right="query_agent_behavior 查 audit/行为链,支持 agent_id 过滤;是 FR-10 专用 Tool。"
|
||
data-explanation-wrong="customer_context 是客户档案+L3;行为链是独立 Tool。">
|
||
<h3 class="quiz-question">风控对话里问「代理人 STAFF-Q 有没有越权记录」,应触发哪个 Tool?</h3>
|
||
<div class="quiz-options">
|
||
<button class="quiz-option" data-value="option-a" onclick="selectOption(this)">
|
||
<div class="quiz-option-radio"></div><span>query_agent_behavior</span>
|
||
</button>
|
||
<button class="quiz-option" data-value="option-b" onclick="selectOption(this)">
|
||
<div class="quiz-option-radio"></div><span>customer_context</span>
|
||
</button>
|
||
<button class="quiz-option" data-value="option-c" onclick="selectOption(this)">
|
||
<div class="quiz-option-radio"></div><span>aml_lookup</span>
|
||
</button>
|
||
</div>
|
||
<div class="quiz-feedback"></div>
|
||
</div>
|
||
<button class="quiz-check-btn" onclick="checkQuiz('quiz-risk-m3')">检查答案</button>
|
||
<button class="quiz-reset-btn" onclick="resetQuiz('quiz-risk-m3')">重做</button>
|
||
</div>
|
||
|
||
<p style="margin-top:2rem; font-size:0.9rem; color: var(--color-text-muted);">
|
||
仓库现状(2026-09):STAFF-30001 含 risk_demo;<code>python -m pytest</code> → 774 passed。
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</main>
|
||
|
||
</body>
|
||
</html>
|