11659 lines
733 KiB
HTML
11659 lines
733 KiB
HTML
{% extends "ewoooc_base.html" %}
|
||
|
||
{% block title %}市場情報|EwoooC{% endblock %}
|
||
|
||
{% block extra_css %}
|
||
<style>
|
||
.market-intel-status {
|
||
display: grid;
|
||
gap: 1rem;
|
||
min-width: 0;
|
||
max-width: 100%;
|
||
}
|
||
|
||
.market-intel-panel {
|
||
box-sizing: border-box;
|
||
min-width: 0;
|
||
max-width: 100%;
|
||
overflow: hidden;
|
||
border: 1px solid var(--momo-border, #d8c8aa);
|
||
border-radius: 8px;
|
||
background:
|
||
radial-gradient(circle at 1px 1px, rgba(120, 83, 44, 0.16) 1px, transparent 1.35px),
|
||
var(--momo-paper, #f8f2e7);
|
||
background-size: 10px 10px, auto;
|
||
box-shadow: var(--momo-shadow-sm, 0 8px 18px rgba(72, 49, 28, 0.08));
|
||
padding: 1.25rem;
|
||
}
|
||
|
||
.market-intel-title {
|
||
color: var(--momo-ink, #30251b);
|
||
font-size: 1.35rem;
|
||
font-weight: 800;
|
||
margin: 0;
|
||
}
|
||
|
||
.market-intel-muted {
|
||
color: var(--momo-muted, #756a5b);
|
||
margin: 0;
|
||
overflow-wrap: anywhere;
|
||
}
|
||
|
||
.market-intel-flags {
|
||
display: grid;
|
||
gap: 0.75rem;
|
||
grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr));
|
||
margin-top: 1rem;
|
||
min-width: 0;
|
||
}
|
||
|
||
.market-intel-flag {
|
||
min-width: 0;
|
||
border-left: 3px solid var(--momo-accent, #c8752d);
|
||
background: rgba(255, 250, 241, 0.84);
|
||
padding: 0.8rem 0.9rem;
|
||
}
|
||
|
||
.market-intel-flag span {
|
||
color: var(--momo-muted, #756a5b);
|
||
display: block;
|
||
font-size: 0.78rem;
|
||
font-weight: 700;
|
||
letter-spacing: 0;
|
||
}
|
||
|
||
.market-intel-flag strong {
|
||
color: var(--momo-ink, #30251b);
|
||
font-family: "JetBrains Mono", monospace;
|
||
font-size: 1rem;
|
||
}
|
||
|
||
.market-intel-preview-head {
|
||
align-items: center;
|
||
display: flex;
|
||
gap: 0.75rem;
|
||
justify-content: space-between;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.market-intel-preview-title {
|
||
color: var(--momo-ink, #30251b);
|
||
font-size: 1rem;
|
||
font-weight: 800;
|
||
margin: 0;
|
||
}
|
||
|
||
.market-intel-icon-button {
|
||
align-items: center;
|
||
background: rgba(255, 250, 241, 0.9);
|
||
border: 1px solid var(--momo-border, #d8c8aa);
|
||
border-radius: 8px;
|
||
color: var(--momo-ink, #30251b);
|
||
display: inline-flex;
|
||
height: 2.25rem;
|
||
justify-content: center;
|
||
width: 2.25rem;
|
||
}
|
||
|
||
.market-intel-icon-button:hover {
|
||
background: rgba(201, 117, 45, 0.12);
|
||
color: var(--momo-accent-700, #8f4530);
|
||
}
|
||
|
||
.market-intel-preview-meta {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 0.5rem;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.market-intel-pill {
|
||
min-width: 0;
|
||
background: rgba(255, 250, 241, 0.82);
|
||
border: 1px solid rgba(120, 83, 44, 0.14);
|
||
border-radius: 8px;
|
||
color: var(--momo-muted, #756a5b);
|
||
font-family: "JetBrains Mono", monospace;
|
||
font-size: 0.78rem;
|
||
font-weight: 700;
|
||
overflow-wrap: anywhere;
|
||
padding: 0.35rem 0.5rem;
|
||
}
|
||
|
||
.market-intel-empty {
|
||
background: rgba(255, 250, 241, 0.72);
|
||
border: 1px dashed rgba(120, 83, 44, 0.28);
|
||
border-radius: 8px;
|
||
color: var(--momo-muted, #756a5b);
|
||
padding: 1rem;
|
||
}
|
||
|
||
.market-intel-candidate-list {
|
||
display: grid;
|
||
gap: 0.75rem;
|
||
min-width: 0;
|
||
}
|
||
|
||
.market-intel-candidate {
|
||
min-width: 0;
|
||
background: rgba(255, 250, 241, 0.82);
|
||
border: 1px solid rgba(120, 83, 44, 0.14);
|
||
border-left: 3px solid var(--momo-accent, #c8752d);
|
||
border-radius: 8px;
|
||
padding: 0.8rem 0.9rem;
|
||
}
|
||
|
||
.market-intel-candidate a {
|
||
color: var(--momo-ink, #30251b);
|
||
font-weight: 800;
|
||
text-decoration: none;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.market-intel-candidate small {
|
||
color: var(--momo-muted, #756a5b);
|
||
display: block;
|
||
margin-top: 0.35rem;
|
||
}
|
||
|
||
.market-intel-operation-list {
|
||
display: grid;
|
||
gap: 0.75rem;
|
||
min-width: 0;
|
||
}
|
||
|
||
.market-intel-operation {
|
||
min-width: 0;
|
||
background: rgba(255, 250, 241, 0.82);
|
||
border: 1px solid rgba(120, 83, 44, 0.14);
|
||
border-left: 3px solid var(--momo-accent, #c8752d);
|
||
border-radius: 8px;
|
||
padding: 0.8rem 0.9rem;
|
||
}
|
||
|
||
.market-intel-operation strong {
|
||
color: var(--momo-ink, #30251b);
|
||
display: block;
|
||
font-family: "JetBrains Mono", monospace;
|
||
font-size: 0.92rem;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.market-intel-operation small {
|
||
color: var(--momo-muted, #756a5b);
|
||
display: block;
|
||
margin-top: 0.35rem;
|
||
}
|
||
|
||
.market-intel-check-list {
|
||
display: grid;
|
||
gap: 0.6rem;
|
||
min-width: 0;
|
||
}
|
||
|
||
.market-intel-deploy-grid {
|
||
display: grid;
|
||
gap: 0.9rem;
|
||
grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
|
||
margin-top: 1rem;
|
||
min-width: 0;
|
||
}
|
||
|
||
.market-intel-deploy-section-title {
|
||
color: var(--momo-muted, #756a5b);
|
||
font-family: "JetBrains Mono", monospace;
|
||
font-size: 0.78rem;
|
||
font-weight: 800;
|
||
margin: 0 0 0.55rem;
|
||
}
|
||
|
||
.market-intel-check {
|
||
align-items: center;
|
||
background: rgba(255, 250, 241, 0.82);
|
||
border: 1px solid rgba(120, 83, 44, 0.14);
|
||
border-radius: 8px;
|
||
color: var(--momo-muted, #756a5b);
|
||
display: flex;
|
||
gap: 0.65rem;
|
||
justify-content: space-between;
|
||
min-width: 0;
|
||
padding: 0.65rem 0.75rem;
|
||
}
|
||
|
||
.market-intel-check div {
|
||
min-width: 0;
|
||
}
|
||
|
||
.market-intel-check strong {
|
||
color: var(--momo-ink, #30251b);
|
||
font-family: "JetBrains Mono", monospace;
|
||
font-size: 0.84rem;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.market-intel-check small {
|
||
color: var(--momo-muted, #756a5b);
|
||
display: block;
|
||
line-height: 1.45;
|
||
margin-top: 0.25rem;
|
||
overflow-wrap: anywhere;
|
||
}
|
||
|
||
.market-intel-check span {
|
||
color: var(--momo-muted, #756a5b);
|
||
font-family: "JetBrains Mono", monospace;
|
||
font-size: 0.78rem;
|
||
font-weight: 800;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.market-intel-control-row {
|
||
align-items: stretch;
|
||
display: grid;
|
||
gap: 0.75rem;
|
||
grid-template-columns: minmax(0, 1fr) auto;
|
||
min-width: 0;
|
||
}
|
||
|
||
.market-intel-control-actions {
|
||
align-content: flex-start;
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 0.75rem;
|
||
justify-content: flex-end;
|
||
min-width: 0;
|
||
max-width: min(34rem, 100%);
|
||
}
|
||
|
||
.market-intel-json-input {
|
||
background: rgba(255, 250, 241, 0.9);
|
||
border: 1px solid var(--momo-border, #d8c8aa);
|
||
border-radius: 8px;
|
||
color: var(--momo-ink, #30251b);
|
||
font-family: "JetBrains Mono", monospace;
|
||
font-size: 0.82rem;
|
||
line-height: 1.55;
|
||
min-width: 0;
|
||
outline: none;
|
||
padding: 0.75rem;
|
||
resize: vertical;
|
||
width: 100%;
|
||
}
|
||
|
||
.market-intel-json-input:focus {
|
||
border-color: var(--momo-accent, #c8752d);
|
||
box-shadow: 0 0 0 3px rgba(201, 117, 45, 0.14);
|
||
}
|
||
|
||
@media (max-width: 760px) {
|
||
.market-intel-preview-head,
|
||
.market-intel-check,
|
||
.market-intel-control-row {
|
||
align-items: flex-start;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.market-intel-control-row {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.market-intel-control-actions {
|
||
justify-content: flex-start;
|
||
max-width: 100%;
|
||
}
|
||
|
||
.market-intel-panel {
|
||
padding: 1rem;
|
||
}
|
||
|
||
.market-intel-check span {
|
||
max-width: 100%;
|
||
overflow-wrap: anywhere;
|
||
white-space: normal;
|
||
}
|
||
}
|
||
</style>
|
||
{% endblock %}
|
||
|
||
{% block ewooo_content %}
|
||
<section class="market-intel-status">
|
||
<div class="market-intel-panel">
|
||
<p class="market-intel-muted momo-mono mb-2">MARKET INTEL / {{ status.phase }}</p>
|
||
<h1 class="market-intel-title">市場情報尚未啟用</h1>
|
||
<p class="market-intel-muted mt-2">目前只載入安全骨架;爬蟲、正式寫入與排程都尚未掛載。</p>
|
||
|
||
<div class="market-intel-flags">
|
||
<div class="market-intel-flag">
|
||
<span>模組開關</span>
|
||
<strong>{{ 'ON' if status.enabled else 'OFF' }}</strong>
|
||
</div>
|
||
<div class="market-intel-flag">
|
||
<span>爬蟲開關</span>
|
||
<strong>{{ 'ON' if status.crawler_enabled else 'OFF' }}</strong>
|
||
</div>
|
||
<div class="market-intel-flag">
|
||
<span>資料寫入</span>
|
||
<strong>{{ 'ON' if status.write_enabled else 'OFF' }}</strong>
|
||
</div>
|
||
<div class="market-intel-flag">
|
||
<span>排程掛載</span>
|
||
<strong>{{ 'ON' if status.scheduler_attached else 'OFF' }}</strong>
|
||
</div>
|
||
<div class="market-intel-flag">
|
||
<span>DB 寫入許可</span>
|
||
<strong>{{ 'ON' if status.database_write_allowed else 'OFF' }}</strong>
|
||
</div>
|
||
<div class="market-intel-flag">
|
||
<span>已註冊 Adapter</span>
|
||
<strong>{{ adapter_count|default(0) }}</strong>
|
||
</div>
|
||
<div class="market-intel-flag">
|
||
<span>手動 Fetch</span>
|
||
<strong>{{ 'ON' if manual_fetch_allowed|default(false) else 'OFF' }}</strong>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-preview>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">CANDIDATE PREVIEW / SAFE</p>
|
||
<h2 class="market-intel-preview-title">候選預覽</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理候選預覽" data-market-intel-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-preview-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-preview-body>
|
||
<div class="market-intel-empty">讀取候選預覽中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-writer>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">SEED WRITER / DRY RUN</p>
|
||
<h2 class="market-intel-preview-title">平台種子寫入預覽</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理寫入預覽" data-market-intel-writer-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-writer-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-writer-body>
|
||
<div class="market-intel-empty">讀取寫入預覽中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-cli>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">SEED CLI / TRANSACTION PREVIEW</p>
|
||
<h2 class="market-intel-preview-title">Seed CLI 交易預覽</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理交易預覽" data-market-intel-cli-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-cli-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-cli-body>
|
||
<div class="market-intel-empty">讀取交易預覽中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-db-probe>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">DB SCHEMA / READ ONLY PROBE</p>
|
||
<h2 class="market-intel-preview-title">正式 DB Schema 探針</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理 DB 探針" data-market-intel-db-probe-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-db-probe-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-db-probe-body>
|
||
<div class="market-intel-empty">讀取 DB 探針中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-seed-diff>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">PLATFORM SEED / READ ONLY DIFF</p>
|
||
<h2 class="market-intel-preview-title">平台 Seed DB 差異探針</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理 Seed 差異" data-market-intel-seed-diff-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-seed-diff-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-seed-diff-body>
|
||
<div class="market-intel-empty">讀取 Seed 差異探針中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-legacy-bridge>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">LEGACY SOURCE / BRIDGE PREVIEW</p>
|
||
<h2 class="market-intel-preview-title">既有資料橋接預覽</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理橋接預覽" data-market-intel-legacy-bridge-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-legacy-bridge-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-legacy-bridge-body>
|
||
<div class="market-intel-empty">讀取既有資料橋接預覽中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-mcp-readiness>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MCP / READINESS PREVIEW</p>
|
||
<h2 class="market-intel-preview-title">MCP 整合就緒度</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理 MCP 就緒度" data-market-intel-mcp-readiness-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-mcp-readiness-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-mcp-readiness-body>
|
||
<div class="market-intel-empty">讀取 MCP 整合就緒度中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-mcp-preflight>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MCP / EXTERNAL DEPLOY PREFLIGHT</p>
|
||
<h2 class="market-intel-preview-title">外部 MCP 部署預檢</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理 MCP 部署預檢" data-market-intel-mcp-preflight-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-mcp-preflight-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-mcp-preflight-body>
|
||
<div class="market-intel-empty">讀取 MCP 部署預檢中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-mcp-activation>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MCP / ACTIVATION RUNBOOK</p>
|
||
<h2 class="market-intel-preview-title">MCP 啟用 Runbook</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理 MCP 啟用 Runbook" data-market-intel-mcp-activation-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-mcp-activation-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-mcp-activation-body>
|
||
<div class="market-intel-empty">讀取 MCP 啟用 Runbook 中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-mcp-fetch-gate>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MCP / FETCH GATE</p>
|
||
<h2 class="market-intel-preview-title">人工 Fetch 安全閘門</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理人工 Fetch 安全閘門" data-market-intel-mcp-fetch-gate-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-mcp-fetch-gate-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-gate-body>
|
||
<div class="market-intel-empty">讀取人工 Fetch 安全閘門中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-manual-sample>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MANUAL SAMPLE / FETCH PLAN</p>
|
||
<h2 class="market-intel-preview-title">人工樣本 Fetch 計畫</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理人工樣本 Fetch 計畫" data-market-intel-manual-sample-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-manual-sample-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-manual-sample-body>
|
||
<div class="market-intel-empty">讀取人工樣本 Fetch 計畫中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-sample-acceptance>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MANUAL SAMPLE / ACCEPTANCE</p>
|
||
<h2 class="market-intel-preview-title">樣本結果驗收契約</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理樣本結果驗收契約" data-market-intel-sample-acceptance-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-sample-acceptance-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-sample-acceptance-body>
|
||
<div class="market-intel-empty">讀取樣本結果驗收契約中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-sample-review>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MANUAL SAMPLE / REVIEW</p>
|
||
<h2 class="market-intel-preview-title">樣本結果審核預覽</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理樣本結果審核預覽" data-market-intel-sample-review-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-sample-review-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-sample-review-body>
|
||
<div class="market-intel-empty">讀取樣本結果審核預覽中...</div>
|
||
</div>
|
||
<div class="market-intel-control-row mt-3">
|
||
<textarea class="market-intel-json-input" rows="5" spellcheck="false" data-market-intel-sample-review-input placeholder="sample result JSON"></textarea>
|
||
<div class="market-intel-control-actions" data-market-intel-sample-review-actions-rail>
|
||
<button class="market-intel-icon-button" type="button" title="審核 sample result JSON" data-market-intel-sample-review-evaluate>
|
||
<i class="fas fa-check" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="產生候選活動 handoff" data-market-intel-sample-candidate-handoff>
|
||
<i class="fas fa-arrow-right" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="產生候選審核 queue 草案" data-market-intel-sample-candidate-queue-draft>
|
||
<i class="fas fa-clipboard-list" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="檢查 queue 寫入送審 gate" data-market-intel-sample-candidate-queue-approval>
|
||
<i class="fas fa-shield-halved" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="產生 queue transaction preview" data-market-intel-sample-candidate-queue-transaction>
|
||
<i class="fas fa-code" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="檢查 queue writer CLI gate" data-market-intel-sample-candidate-queue-writer>
|
||
<i class="fas fa-terminal" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="檢查 queue writer preflight" data-market-intel-sample-candidate-queue-preflight>
|
||
<i class="fas fa-database" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="檢查 queue writer post-write smoke" data-market-intel-sample-candidate-queue-postwrite-smoke>
|
||
<i class="fas fa-search" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="產生 queue writer operator drill" data-market-intel-sample-candidate-queue-operator-drill>
|
||
<i class="fas fa-list-check" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="產生 queue writer run package" data-market-intel-sample-candidate-queue-run-package>
|
||
<i class="fas fa-box-archive" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="檢查 queue writer run readiness" data-market-intel-sample-candidate-queue-run-readiness>
|
||
<i class="fas fa-clipboard-check" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="審核 queue writer run receipt" data-market-intel-sample-candidate-queue-run-receipt>
|
||
<i class="fas fa-receipt" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="收尾 queue writer run closeout" data-market-intel-sample-candidate-queue-run-closeout>
|
||
<i class="fas fa-flag-checkered" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="產生 queue review handoff" data-market-intel-sample-candidate-queue-review-handoff>
|
||
<i class="fas fa-handshake" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="檢查 queue review inventory" data-market-intel-sample-candidate-queue-review-inventory>
|
||
<i class="fas fa-eye" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="產生 queue review decision 草案" data-market-intel-sample-candidate-queue-review-decision>
|
||
<i class="fas fa-scale-balanced" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="檢查 queue review decision 批准 gate" data-market-intel-sample-candidate-queue-review-decision-approval>
|
||
<i class="fas fa-shield-halved" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="產生 queue review decision transaction preview" data-market-intel-sample-candidate-queue-review-decision-transaction>
|
||
<i class="fas fa-code-branch" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="檢查 queue review decision writer preflight" data-market-intel-sample-candidate-queue-review-decision-preflight>
|
||
<i class="fas fa-database" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="檢查 queue review decision writer post-write smoke" data-market-intel-sample-candidate-queue-review-decision-postwrite-smoke>
|
||
<i class="fas fa-search" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="產生 queue review decision writer operator drill" data-market-intel-sample-candidate-queue-review-decision-operator-drill>
|
||
<i class="fas fa-list-check" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="產生 queue review decision writer run package" data-market-intel-sample-candidate-queue-review-decision-run-package>
|
||
<i class="fas fa-box-archive" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="檢查 queue review decision writer run readiness" data-market-intel-sample-candidate-queue-review-decision-run-readiness>
|
||
<i class="fas fa-clipboard-check" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="檢查 queue review decision writer CLI gate" data-market-intel-sample-candidate-queue-review-decision-writer>
|
||
<i class="fas fa-terminal" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="審核 queue review decision writer run receipt" data-market-intel-sample-candidate-queue-review-decision-run-receipt>
|
||
<i class="fas fa-receipt" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="收尾 queue review decision writer run closeout" data-market-intel-sample-candidate-queue-review-decision-run-closeout>
|
||
<i class="fas fa-flag-checkered" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="檢查 queue review decision post-closeout inventory" data-market-intel-sample-candidate-queue-review-decision-post-closeout-inventory>
|
||
<i class="fas fa-clipboard-list" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="封存 queue review completion archive" data-market-intel-sample-candidate-queue-review-completion-archive>
|
||
<i class="fas fa-archive" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="整理 queue review archive summary" data-market-intel-sample-candidate-queue-review-archive-summary>
|
||
<i class="fas fa-file-lines" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="檢查 queue review AI summary preflight" data-market-intel-sample-candidate-queue-review-ai-summary-preflight>
|
||
<i class="fas fa-robot" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="產生 queue review AI summary run package" data-market-intel-sample-candidate-queue-review-ai-summary-run-package>
|
||
<i class="fas fa-box-open" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="驗收 queue review AI summary output receipt" data-market-intel-sample-candidate-queue-review-ai-summary-output-receipt>
|
||
<i class="fas fa-file-circle-check" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="檢查 queue review AI summary persistence preflight" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-preflight>
|
||
<i class="fas fa-file-pen" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="產生 queue review AI summary persistence transaction preview" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-transaction>
|
||
<i class="fas fa-code-branch" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="檢查 queue review AI summary persistence writer preflight" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-writer-preflight>
|
||
<i class="fas fa-database" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="產生 queue review AI summary persistence run package" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-run-package>
|
||
<i class="fas fa-box-archive" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="檢查 queue review AI summary persistence run readiness" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-run-readiness>
|
||
<i class="fas fa-clipboard-check" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="審核 queue review AI summary persistence run receipt" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-run-receipt>
|
||
<i class="fas fa-receipt" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="收尾 queue review AI summary persistence run closeout" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-run-closeout>
|
||
<i class="fas fa-flag-checkered" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="檢查 queue review AI summary Telegram dispatch gate" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-gate>
|
||
<i class="fas fa-paper-plane" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="產生 queue review AI summary Telegram dispatch run package" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-run-package>
|
||
<i class="fas fa-envelope-open-text" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="檢查 queue review AI summary Telegram dispatch run readiness" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-run-readiness>
|
||
<i class="fas fa-user-check" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="審核 queue review AI summary Telegram dispatch run receipt" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-run-receipt>
|
||
<i class="fas fa-receipt" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="收尾 queue review AI summary Telegram dispatch closeout" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-closeout>
|
||
<i class="fas fa-flag-checkered" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="封存 queue review AI summary Telegram dispatch archive" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-archive>
|
||
<i class="fas fa-folder-tree" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="整理 queue review AI summary Telegram dispatch archive summary" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-archive-summary>
|
||
<i class="fas fa-file-lines" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="整理 queue review AI summary Telegram dispatch report input" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-input>
|
||
<i class="fas fa-chart-simple" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="產生 queue review AI summary Telegram dispatch report run package" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-run-package>
|
||
<i class="fas fa-box-open" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="檢查 queue review AI summary Telegram dispatch report run readiness" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-run-readiness>
|
||
<i class="fas fa-clipboard-check" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="審核 queue review AI summary Telegram dispatch report run receipt" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-run-receipt>
|
||
<i class="fas fa-receipt" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="收尾 queue review AI summary Telegram dispatch report closeout" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-closeout>
|
||
<i class="fas fa-flag-checkered" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="封存 queue review AI summary Telegram dispatch report archive" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-archive>
|
||
<i class="fas fa-box-archive" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="整理 queue review AI summary Telegram dispatch report archive summary" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-archive-summary>
|
||
<i class="fas fa-file-signature" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="整理 queue review AI summary Telegram dispatch report catalog handoff" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-handoff>
|
||
<i class="fas fa-table-list" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="整理 queue review AI summary Telegram dispatch report catalog index" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-index>
|
||
<i class="fas fa-list-ol" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="檢查 queue review AI summary Telegram dispatch report catalog write preflight" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-write-preflight>
|
||
<i class="fas fa-file-shield" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="檢查 queue review AI summary Telegram dispatch report catalog record write gate" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-record-write>
|
||
<i class="fas fa-database" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="產生 queue review AI summary Telegram dispatch report catalog record run package" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-record-run-package>
|
||
<i class="fas fa-box" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="檢查 queue review AI summary Telegram dispatch report catalog record run readiness" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-record-run-readiness>
|
||
<i class="fas fa-clipboard-check" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="審核 queue review AI summary Telegram dispatch report catalog record run receipt" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-record-run-receipt>
|
||
<i class="fas fa-receipt" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="審核 queue review AI summary Telegram dispatch report catalog record commit gate" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-record-commit>
|
||
<i class="fas fa-lock" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="收尾 queue review AI summary Telegram dispatch report catalog record closeout gate" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-record-closeout>
|
||
<i class="fas fa-flag-checkered" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="封存 queue review AI summary Telegram dispatch report catalog record archive gate" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-record-archive>
|
||
<i class="fas fa-box-archive" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="整理 queue review AI summary Telegram dispatch report catalog record archive summary gate" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-record-archive-summary>
|
||
<i class="fas fa-file-signature" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="收尾 queue review AI summary Telegram dispatch report catalog record final closeout gate" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-record-final-closeout>
|
||
<i class="fas fa-circle-check" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-scheduler>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">SCHEDULER / ATTACH PLAN</p>
|
||
<h2 class="market-intel-preview-title">排程掛載計畫</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理排程掛載計畫" data-market-intel-scheduler-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-scheduler-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-scheduler-body>
|
||
<div class="market-intel-empty">讀取排程掛載計畫中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-match-review>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MATCH REVIEW / PLAN</p>
|
||
<h2 class="market-intel-preview-title">商品比對審核計畫</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理商品比對審核計畫" data-market-intel-match-review-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-match-review-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-match-review-body>
|
||
<div class="market-intel-empty">讀取商品比對審核計畫中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-opportunity>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">OPPORTUNITY / THREAT PLAN</p>
|
||
<h2 class="market-intel-preview-title">市場機會與威脅計畫</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理市場機會與威脅計畫" data-market-intel-opportunity-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-opportunity-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-opportunity-body>
|
||
<div class="market-intel-empty">讀取市場機會與威脅計畫中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-opportunity-scoring>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">OPPORTUNITY / SCORING MODEL</p>
|
||
<h2 class="market-intel-preview-title">機會威脅分數模型</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理機會威脅分數模型" data-market-intel-opportunity-scoring-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-opportunity-scoring-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-opportunity-scoring-body>
|
||
<div class="market-intel-empty">讀取機會威脅分數模型中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-opportunity-evidence>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">OPPORTUNITY / EVIDENCE BUNDLE</p>
|
||
<h2 class="market-intel-preview-title">機會威脅證據包</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理機會威脅證據包" data-market-intel-opportunity-evidence-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-opportunity-evidence-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-opportunity-evidence-body>
|
||
<div class="market-intel-empty">讀取機會威脅證據包中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-opportunity-alert>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">OPPORTUNITY / ALERT CANDIDATE</p>
|
||
<h2 class="market-intel-preview-title">機會威脅告警候選</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理機會威脅告警候選" data-market-intel-opportunity-alert-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-opportunity-alert-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-opportunity-alert-body>
|
||
<div class="market-intel-empty">讀取機會威脅告警候選中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-migration>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MIGRATION / BLUEPRINT</p>
|
||
<h2 class="market-intel-preview-title">Schema migration 草案</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理 migration 草案" data-market-intel-migration-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-migration-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-migration-body>
|
||
<div class="market-intel-empty">讀取 migration 草案中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-migration-drill>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MIGRATION / APPLY DRILL</p>
|
||
<h2 class="market-intel-preview-title">Migration 套用演練</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理 migration 套用演練" data-market-intel-migration-drill-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-migration-drill-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-migration-drill-body>
|
||
<div class="market-intel-empty">讀取 migration 套用演練中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-catalog-review>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MIGRATION / CATALOG REVIEW</p>
|
||
<h2 class="market-intel-preview-title">正式 DB catalog 判讀</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理正式 DB catalog 判讀" data-market-intel-catalog-review-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-catalog-review-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-catalog-review-body>
|
||
<div class="market-intel-empty">讀取正式 DB catalog 判讀中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-live-smoke>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MIGRATION / LIVE SMOKE</p>
|
||
<h2 class="market-intel-preview-title">正式 DB 只讀 smoke</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理正式 DB 只讀 smoke" data-market-intel-live-smoke-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-live-smoke-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-live-smoke-body>
|
||
<div class="market-intel-empty">讀取正式 DB 只讀 smoke 中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-live-inventory>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">DB INVENTORY / READ ONLY</p>
|
||
<h2 class="market-intel-preview-title">正式 DB 庫存總覽</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理正式 DB 庫存總覽" data-market-intel-live-inventory-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-live-inventory-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-live-inventory-body>
|
||
<div class="market-intel-empty">讀取正式 DB 庫存總覽中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-approval>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">WRITE APPROVAL / RUNBOOK</p>
|
||
<h2 class="market-intel-preview-title">正式寫入批准檢查</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理批准檢查" data-market-intel-approval-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-approval-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-approval-body>
|
||
<div class="market-intel-empty">讀取批准檢查中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-deploy>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">DEPLOYMENT / READINESS</p>
|
||
<h2 class="market-intel-preview-title">推版準備檢查</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理推版準備" data-market-intel-deploy-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-deploy-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-deploy-body>
|
||
<div class="market-intel-empty">讀取推版準備中...</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
{% endblock %}
|
||
|
||
{% block extra_js %}
|
||
<script>
|
||
(function () {
|
||
const root = document.querySelector('[data-market-intel-preview]');
|
||
const writerRoot = document.querySelector('[data-market-intel-writer]');
|
||
const cliRoot = document.querySelector('[data-market-intel-cli]');
|
||
const dbProbeRoot = document.querySelector('[data-market-intel-db-probe]');
|
||
const seedDiffRoot = document.querySelector('[data-market-intel-seed-diff]');
|
||
const legacyBridgeRoot = document.querySelector('[data-market-intel-legacy-bridge]');
|
||
const mcpReadinessRoot = document.querySelector('[data-market-intel-mcp-readiness]');
|
||
const mcpPreflightRoot = document.querySelector('[data-market-intel-mcp-preflight]');
|
||
const mcpActivationRoot = document.querySelector('[data-market-intel-mcp-activation]');
|
||
const mcpFetchGateRoot = document.querySelector('[data-market-intel-mcp-fetch-gate]');
|
||
const manualSampleRoot = document.querySelector('[data-market-intel-manual-sample]');
|
||
const sampleAcceptanceRoot = document.querySelector('[data-market-intel-sample-acceptance]');
|
||
const sampleReviewRoot = document.querySelector('[data-market-intel-sample-review]');
|
||
const schedulerRoot = document.querySelector('[data-market-intel-scheduler]');
|
||
const matchReviewRoot = document.querySelector('[data-market-intel-match-review]');
|
||
const opportunityRoot = document.querySelector('[data-market-intel-opportunity]');
|
||
const opportunityScoringRoot = document.querySelector('[data-market-intel-opportunity-scoring]');
|
||
const opportunityEvidenceRoot = document.querySelector('[data-market-intel-opportunity-evidence]');
|
||
const opportunityAlertRoot = document.querySelector('[data-market-intel-opportunity-alert]');
|
||
const migrationRoot = document.querySelector('[data-market-intel-migration]');
|
||
const migrationDrillRoot = document.querySelector('[data-market-intel-migration-drill]');
|
||
const catalogReviewRoot = document.querySelector('[data-market-intel-catalog-review]');
|
||
const liveSmokeRoot = document.querySelector('[data-market-intel-live-smoke]');
|
||
const liveInventoryRoot = document.querySelector('[data-market-intel-live-inventory]');
|
||
const approvalRoot = document.querySelector('[data-market-intel-approval]');
|
||
const deployRoot = document.querySelector('[data-market-intel-deploy]');
|
||
if (!root && !writerRoot && !cliRoot && !dbProbeRoot && !seedDiffRoot && !legacyBridgeRoot && !mcpReadinessRoot && !mcpPreflightRoot && !mcpActivationRoot && !mcpFetchGateRoot && !manualSampleRoot && !sampleAcceptanceRoot && !sampleReviewRoot && !schedulerRoot && !matchReviewRoot && !opportunityRoot && !opportunityScoringRoot && !opportunityEvidenceRoot && !opportunityAlertRoot && !migrationRoot && !migrationDrillRoot && !catalogReviewRoot && !liveSmokeRoot && !liveInventoryRoot && !approvalRoot && !deployRoot) return;
|
||
|
||
const meta = root ? root.querySelector('[data-market-intel-preview-meta]') : null;
|
||
const body = root ? root.querySelector('[data-market-intel-preview-body]') : null;
|
||
const refresh = root ? root.querySelector('[data-market-intel-refresh]') : null;
|
||
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '';
|
||
const endpoint = "{{ url_for('market_intel.market_intel_candidate_preview') }}?fetch=false&limit=20";
|
||
const writerMeta = writerRoot ? writerRoot.querySelector('[data-market-intel-writer-meta]') : null;
|
||
const writerBody = writerRoot ? writerRoot.querySelector('[data-market-intel-writer-body]') : null;
|
||
const writerRefresh = writerRoot ? writerRoot.querySelector('[data-market-intel-writer-refresh]') : null;
|
||
const writerEndpoint = "{{ url_for('market_intel.market_intel_platform_seed_writer_plan') }}";
|
||
const cliMeta = cliRoot ? cliRoot.querySelector('[data-market-intel-cli-meta]') : null;
|
||
const cliBody = cliRoot ? cliRoot.querySelector('[data-market-intel-cli-body]') : null;
|
||
const cliRefresh = cliRoot ? cliRoot.querySelector('[data-market-intel-cli-refresh]') : null;
|
||
const cliEndpoint = "{{ url_for('market_intel.market_intel_seed_writer_cli_status') }}";
|
||
const dbProbeMeta = dbProbeRoot ? dbProbeRoot.querySelector('[data-market-intel-db-probe-meta]') : null;
|
||
const dbProbeBody = dbProbeRoot ? dbProbeRoot.querySelector('[data-market-intel-db-probe-body]') : null;
|
||
const dbProbeRefresh = dbProbeRoot ? dbProbeRoot.querySelector('[data-market-intel-db-probe-refresh]') : null;
|
||
const dbProbeEndpoint = "{{ url_for('market_intel.market_intel_schema_db_probe') }}?execute=false";
|
||
const seedDiffMeta = seedDiffRoot ? seedDiffRoot.querySelector('[data-market-intel-seed-diff-meta]') : null;
|
||
const seedDiffBody = seedDiffRoot ? seedDiffRoot.querySelector('[data-market-intel-seed-diff-body]') : null;
|
||
const seedDiffRefresh = seedDiffRoot ? seedDiffRoot.querySelector('[data-market-intel-seed-diff-refresh]') : null;
|
||
const seedDiffEndpoint = "{{ url_for('market_intel.market_intel_platform_seed_db_diff') }}?execute=false";
|
||
const legacyBridgeMeta = legacyBridgeRoot ? legacyBridgeRoot.querySelector('[data-market-intel-legacy-bridge-meta]') : null;
|
||
const legacyBridgeBody = legacyBridgeRoot ? legacyBridgeRoot.querySelector('[data-market-intel-legacy-bridge-body]') : null;
|
||
const legacyBridgeRefresh = legacyBridgeRoot ? legacyBridgeRoot.querySelector('[data-market-intel-legacy-bridge-refresh]') : null;
|
||
const legacyBridgeEndpoint = "{{ url_for('market_intel.market_intel_legacy_source_bridge') }}?execute=false&limit=5";
|
||
const mcpReadinessMeta = mcpReadinessRoot ? mcpReadinessRoot.querySelector('[data-market-intel-mcp-readiness-meta]') : null;
|
||
const mcpReadinessBody = mcpReadinessRoot ? mcpReadinessRoot.querySelector('[data-market-intel-mcp-readiness-body]') : null;
|
||
const mcpReadinessRefresh = mcpReadinessRoot ? mcpReadinessRoot.querySelector('[data-market-intel-mcp-readiness-refresh]') : null;
|
||
const mcpReadinessEndpoint = "{{ url_for('market_intel.market_intel_mcp_readiness') }}?execute=false&timeout=3";
|
||
const mcpPreflightMeta = mcpPreflightRoot ? mcpPreflightRoot.querySelector('[data-market-intel-mcp-preflight-meta]') : null;
|
||
const mcpPreflightBody = mcpPreflightRoot ? mcpPreflightRoot.querySelector('[data-market-intel-mcp-preflight-body]') : null;
|
||
const mcpPreflightRefresh = mcpPreflightRoot ? mcpPreflightRoot.querySelector('[data-market-intel-mcp-preflight-refresh]') : null;
|
||
const mcpPreflightEndpoint = "{{ url_for('market_intel.market_intel_mcp_deploy_preflight') }}";
|
||
const mcpActivationMeta = mcpActivationRoot ? mcpActivationRoot.querySelector('[data-market-intel-mcp-activation-meta]') : null;
|
||
const mcpActivationBody = mcpActivationRoot ? mcpActivationRoot.querySelector('[data-market-intel-mcp-activation-body]') : null;
|
||
const mcpActivationRefresh = mcpActivationRoot ? mcpActivationRoot.querySelector('[data-market-intel-mcp-activation-refresh]') : null;
|
||
const mcpActivationEndpoint = "{{ url_for('market_intel.market_intel_mcp_activation_runbook') }}";
|
||
const mcpFetchGateMeta = mcpFetchGateRoot ? mcpFetchGateRoot.querySelector('[data-market-intel-mcp-fetch-gate-meta]') : null;
|
||
const mcpFetchGateBody = mcpFetchGateRoot ? mcpFetchGateRoot.querySelector('[data-market-intel-mcp-fetch-gate-body]') : null;
|
||
const mcpFetchGateRefresh = mcpFetchGateRoot ? mcpFetchGateRoot.querySelector('[data-market-intel-mcp-fetch-gate-refresh]') : null;
|
||
const mcpFetchGateEndpoint = "{{ url_for('market_intel.market_intel_mcp_fetch_gate') }}?fetch=false&execute=false";
|
||
const manualSampleMeta = manualSampleRoot ? manualSampleRoot.querySelector('[data-market-intel-manual-sample-meta]') : null;
|
||
const manualSampleBody = manualSampleRoot ? manualSampleRoot.querySelector('[data-market-intel-manual-sample-body]') : null;
|
||
const manualSampleRefresh = manualSampleRoot ? manualSampleRoot.querySelector('[data-market-intel-manual-sample-refresh]') : null;
|
||
const manualSampleEndpoint = "{{ url_for('market_intel.market_intel_manual_sample_plan') }}";
|
||
const sampleAcceptanceMeta = sampleAcceptanceRoot ? sampleAcceptanceRoot.querySelector('[data-market-intel-sample-acceptance-meta]') : null;
|
||
const sampleAcceptanceBody = sampleAcceptanceRoot ? sampleAcceptanceRoot.querySelector('[data-market-intel-sample-acceptance-body]') : null;
|
||
const sampleAcceptanceRefresh = sampleAcceptanceRoot ? sampleAcceptanceRoot.querySelector('[data-market-intel-sample-acceptance-refresh]') : null;
|
||
const sampleAcceptanceEndpoint = "{{ url_for('market_intel.market_intel_manual_sample_acceptance') }}";
|
||
const sampleReviewMeta = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-review-meta]') : null;
|
||
const sampleReviewBody = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-review-body]') : null;
|
||
const sampleReviewRefresh = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-review-refresh]') : null;
|
||
const sampleReviewInput = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-review-input]') : null;
|
||
const sampleReviewEvaluate = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-review-evaluate]') : null;
|
||
const sampleCandidateHandoff = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-handoff]') : null;
|
||
const sampleCandidateQueueDraft = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-draft]') : null;
|
||
const sampleCandidateQueueApproval = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-approval]') : null;
|
||
const sampleCandidateQueueTransaction = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-transaction]') : null;
|
||
const sampleCandidateQueueWriter = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-writer]') : null;
|
||
const sampleCandidateQueuePreflight = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-preflight]') : null;
|
||
const sampleCandidateQueuePostwriteSmoke = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-postwrite-smoke]') : null;
|
||
const sampleCandidateQueueOperatorDrill = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-operator-drill]') : null;
|
||
const sampleCandidateQueueRunPackage = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-run-package]') : null;
|
||
const sampleCandidateQueueRunReadiness = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-run-readiness]') : null;
|
||
const sampleCandidateQueueRunReceipt = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-run-receipt]') : null;
|
||
const sampleCandidateQueueRunCloseout = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-run-closeout]') : null;
|
||
const sampleCandidateQueueReviewHandoff = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-handoff]') : null;
|
||
const sampleCandidateQueueReviewInventory = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-inventory]') : null;
|
||
const sampleCandidateQueueReviewDecision = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-decision]') : null;
|
||
const sampleCandidateQueueReviewDecisionApproval = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-decision-approval]') : null;
|
||
const sampleCandidateQueueReviewDecisionTransaction = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-decision-transaction]') : null;
|
||
const sampleCandidateQueueReviewDecisionPreflight = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-decision-preflight]') : null;
|
||
const sampleCandidateQueueReviewDecisionPostwriteSmoke = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-decision-postwrite-smoke]') : null;
|
||
const sampleCandidateQueueReviewDecisionOperatorDrill = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-decision-operator-drill]') : null;
|
||
const sampleCandidateQueueReviewDecisionRunPackage = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-decision-run-package]') : null;
|
||
const sampleCandidateQueueReviewDecisionRunReadiness = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-decision-run-readiness]') : null;
|
||
const sampleCandidateQueueReviewDecisionWriter = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-decision-writer]') : null;
|
||
const sampleCandidateQueueReviewDecisionRunReceipt = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-decision-run-receipt]') : null;
|
||
const sampleCandidateQueueReviewDecisionRunCloseout = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-decision-run-closeout]') : null;
|
||
const sampleCandidateQueueReviewDecisionPostCloseoutInventory = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-decision-post-closeout-inventory]') : null;
|
||
const sampleCandidateQueueReviewCompletionArchive = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-completion-archive]') : null;
|
||
const sampleCandidateQueueReviewArchiveSummary = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-archive-summary]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPreflight = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-preflight]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryRunPackage = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-run-package]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryOutputReceipt = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-output-receipt]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistencePreflight = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-preflight]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTransaction = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-transaction]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceWriterPreflight = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-writer-preflight]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceRunPackage = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-run-package]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceRunReadiness = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-run-readiness]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceRunReceipt = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-run-receipt]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceRunCloseout = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-run-closeout]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchGate = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-gate]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunPackage = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-run-package]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunReadiness = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-run-readiness]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunReceipt = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-run-receipt]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchCloseout = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-closeout]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchArchive = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-archive]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchArchiveSummary = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-archive-summary]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportInput = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-input]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunPackage = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-run-package]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunReadiness = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-run-readiness]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunReceipt = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-run-receipt]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCloseout = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-closeout]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportArchive = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-archive]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportArchiveSummary = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-archive-summary]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogHandoff = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-handoff]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogIndex = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-index]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogWritePreflight = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-write-preflight]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordWrite = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-record-write]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunPackage = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-record-run-package]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunReadiness = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-record-run-readiness]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunReceipt = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-record-run-receipt]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordCommit = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-record-commit]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordCloseout = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-record-closeout]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordArchive = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-record-archive]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordArchiveSummary = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-record-archive-summary]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordFinalCloseout = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-record-final-closeout]') : null;
|
||
const sampleReviewEndpoint = "{{ url_for('market_intel.market_intel_manual_sample_review') }}";
|
||
const sampleReviewEvaluateEndpoint = "{{ url_for('market_intel.market_intel_manual_sample_review_evaluate') }}";
|
||
const sampleCandidateHandoffEndpoint = "{{ url_for('market_intel.market_intel_manual_sample_candidate_handoff') }}";
|
||
const sampleCandidateQueueDraftEndpoint = "{{ url_for('market_intel.market_intel_manual_sample_candidate_queue_draft') }}";
|
||
const sampleCandidateQueueApprovalEndpoint = "{{ url_for('market_intel.market_intel_manual_sample_candidate_queue_approval') }}";
|
||
const sampleCandidateQueueTransactionEndpoint = "{{ url_for('market_intel.market_intel_manual_sample_candidate_queue_transaction') }}";
|
||
const sampleCandidateQueueWriterEndpoint = "{{ url_for('market_intel.market_intel_manual_sample_candidate_queue_writer_status') }}";
|
||
const sampleCandidateQueuePreflightEndpoint = "{{ url_for('market_intel.market_intel_manual_sample_candidate_queue_writer_preflight') }}";
|
||
const sampleCandidateQueuePostwriteSmokeEndpoint = "{{ url_for('market_intel.market_intel_manual_sample_candidate_queue_writer_postwrite_smoke') }}";
|
||
const sampleCandidateQueueOperatorDrillEndpoint = "{{ url_for('market_intel.market_intel_manual_sample_candidate_queue_writer_operator_drill') }}";
|
||
const sampleCandidateQueueRunPackageEndpoint = "{{ url_for('market_intel.market_intel_manual_sample_candidate_queue_writer_run_package') }}";
|
||
const sampleCandidateQueueRunReadinessEndpoint = "{{ url_for('market_intel.market_intel_manual_sample_candidate_queue_writer_run_readiness') }}";
|
||
const sampleCandidateQueueRunReceiptEndpoint = "{{ url_for('market_intel.market_intel_manual_sample_candidate_queue_writer_run_receipt') }}";
|
||
const sampleCandidateQueueRunCloseoutEndpoint = "{{ url_for('market_intel.market_intel_manual_sample_candidate_queue_writer_run_closeout') }}";
|
||
const sampleCandidateQueueReviewHandoffEndpoint = "{{ url_for('market_intel.market_intel_manual_sample_candidate_queue_review_handoff') }}";
|
||
const sampleCandidateQueueReviewInventoryEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_inventory') }}";
|
||
const sampleCandidateQueueReviewDecisionEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_decision') }}";
|
||
const sampleCandidateQueueReviewDecisionApprovalEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_decision_approval') }}";
|
||
const sampleCandidateQueueReviewDecisionTransactionEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_decision_transaction') }}";
|
||
const sampleCandidateQueueReviewDecisionPreflightEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_decision_writer_preflight') }}";
|
||
const sampleCandidateQueueReviewDecisionPostwriteSmokeEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_decision_writer_postwrite_smoke') }}";
|
||
const sampleCandidateQueueReviewDecisionOperatorDrillEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_decision_writer_operator_drill') }}";
|
||
const sampleCandidateQueueReviewDecisionRunPackageEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_decision_writer_run_package') }}";
|
||
const sampleCandidateQueueReviewDecisionRunReadinessEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_decision_writer_run_readiness') }}";
|
||
const sampleCandidateQueueReviewDecisionWriterEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_decision_writer_status') }}";
|
||
const sampleCandidateQueueReviewDecisionRunReceiptEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_decision_writer_run_receipt') }}";
|
||
const sampleCandidateQueueReviewDecisionRunCloseoutEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_decision_writer_run_closeout') }}";
|
||
const sampleCandidateQueueReviewDecisionPostCloseoutInventoryEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_decision_post_closeout_inventory') }}";
|
||
const sampleCandidateQueueReviewCompletionArchiveEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_completion_archive') }}";
|
||
const sampleCandidateQueueReviewArchiveSummaryEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_archive_summary') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPreflightEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_preflight') }}";
|
||
const sampleCandidateQueueReviewAiSummaryRunPackageEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_run_package') }}";
|
||
const sampleCandidateQueueReviewAiSummaryOutputReceiptEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_output_receipt') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistencePreflightEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_preflight') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTransactionEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_transaction') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceWriterPreflightEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_writer_preflight') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceRunPackageEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_run_package') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceRunReadinessEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_run_readiness') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceRunReceiptEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_run_receipt') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceRunCloseoutEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_run_closeout') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchGateEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_gate') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunPackageEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_run_package') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunReadinessEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_run_readiness') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunReceiptEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_run_receipt') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchCloseoutEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_closeout') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchArchiveEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_archive') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchArchiveSummaryEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_archive_summary') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportInputEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_input') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunPackageEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_run_package') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunReadinessEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_run_readiness') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunReceiptEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_run_receipt') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCloseoutEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_closeout') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportArchiveEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_archive') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportArchiveSummaryEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_archive_summary') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogHandoffEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_handoff') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogIndexEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_index') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogWritePreflightEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_write_preflight') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordWriteEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_write') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunPackageEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_run_package') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunReadinessEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_run_readiness') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunReceiptEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_run_receipt') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordCommitEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_commit') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordCloseoutEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_closeout') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordArchiveEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_archive') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordArchiveSummaryEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_archive_summary') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordFinalCloseoutEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_final_closeout') }}";
|
||
const schedulerMeta = schedulerRoot ? schedulerRoot.querySelector('[data-market-intel-scheduler-meta]') : null;
|
||
const schedulerBody = schedulerRoot ? schedulerRoot.querySelector('[data-market-intel-scheduler-body]') : null;
|
||
const schedulerRefresh = schedulerRoot ? schedulerRoot.querySelector('[data-market-intel-scheduler-refresh]') : null;
|
||
const schedulerEndpoint = "{{ url_for('market_intel.market_intel_scheduler_plan') }}";
|
||
const matchReviewMeta = matchReviewRoot ? matchReviewRoot.querySelector('[data-market-intel-match-review-meta]') : null;
|
||
const matchReviewBody = matchReviewRoot ? matchReviewRoot.querySelector('[data-market-intel-match-review-body]') : null;
|
||
const matchReviewRefresh = matchReviewRoot ? matchReviewRoot.querySelector('[data-market-intel-match-review-refresh]') : null;
|
||
const matchReviewEndpoint = "{{ url_for('market_intel.market_intel_match_review_plan') }}";
|
||
const opportunityMeta = opportunityRoot ? opportunityRoot.querySelector('[data-market-intel-opportunity-meta]') : null;
|
||
const opportunityBody = opportunityRoot ? opportunityRoot.querySelector('[data-market-intel-opportunity-body]') : null;
|
||
const opportunityRefresh = opportunityRoot ? opportunityRoot.querySelector('[data-market-intel-opportunity-refresh]') : null;
|
||
const opportunityEndpoint = "{{ url_for('market_intel.market_intel_opportunity_plan') }}";
|
||
const opportunityScoringMeta = opportunityScoringRoot ? opportunityScoringRoot.querySelector('[data-market-intel-opportunity-scoring-meta]') : null;
|
||
const opportunityScoringBody = opportunityScoringRoot ? opportunityScoringRoot.querySelector('[data-market-intel-opportunity-scoring-body]') : null;
|
||
const opportunityScoringRefresh = opportunityScoringRoot ? opportunityScoringRoot.querySelector('[data-market-intel-opportunity-scoring-refresh]') : null;
|
||
const opportunityScoringEndpoint = "{{ url_for('market_intel.market_intel_opportunity_scoring_plan') }}";
|
||
const opportunityEvidenceMeta = opportunityEvidenceRoot ? opportunityEvidenceRoot.querySelector('[data-market-intel-opportunity-evidence-meta]') : null;
|
||
const opportunityEvidenceBody = opportunityEvidenceRoot ? opportunityEvidenceRoot.querySelector('[data-market-intel-opportunity-evidence-body]') : null;
|
||
const opportunityEvidenceRefresh = opportunityEvidenceRoot ? opportunityEvidenceRoot.querySelector('[data-market-intel-opportunity-evidence-refresh]') : null;
|
||
const opportunityEvidenceEndpoint = "{{ url_for('market_intel.market_intel_opportunity_evidence_plan') }}";
|
||
const opportunityAlertMeta = opportunityAlertRoot ? opportunityAlertRoot.querySelector('[data-market-intel-opportunity-alert-meta]') : null;
|
||
const opportunityAlertBody = opportunityAlertRoot ? opportunityAlertRoot.querySelector('[data-market-intel-opportunity-alert-body]') : null;
|
||
const opportunityAlertRefresh = opportunityAlertRoot ? opportunityAlertRoot.querySelector('[data-market-intel-opportunity-alert-refresh]') : null;
|
||
const opportunityAlertEndpoint = "{{ url_for('market_intel.market_intel_opportunity_alert_plan') }}";
|
||
const migrationMeta = migrationRoot ? migrationRoot.querySelector('[data-market-intel-migration-meta]') : null;
|
||
const migrationBody = migrationRoot ? migrationRoot.querySelector('[data-market-intel-migration-body]') : null;
|
||
const migrationRefresh = migrationRoot ? migrationRoot.querySelector('[data-market-intel-migration-refresh]') : null;
|
||
const migrationEndpoint = "{{ url_for('market_intel.market_intel_migration_blueprint') }}";
|
||
const migrationDrillMeta = migrationDrillRoot ? migrationDrillRoot.querySelector('[data-market-intel-migration-drill-meta]') : null;
|
||
const migrationDrillBody = migrationDrillRoot ? migrationDrillRoot.querySelector('[data-market-intel-migration-drill-body]') : null;
|
||
const migrationDrillRefresh = migrationDrillRoot ? migrationDrillRoot.querySelector('[data-market-intel-migration-drill-refresh]') : null;
|
||
const migrationDrillEndpoint = "{{ url_for('market_intel.market_intel_migration_apply_drill') }}?execute=false";
|
||
const catalogReviewMeta = catalogReviewRoot ? catalogReviewRoot.querySelector('[data-market-intel-catalog-review-meta]') : null;
|
||
const catalogReviewBody = catalogReviewRoot ? catalogReviewRoot.querySelector('[data-market-intel-catalog-review-body]') : null;
|
||
const catalogReviewRefresh = catalogReviewRoot ? catalogReviewRoot.querySelector('[data-market-intel-catalog-review-refresh]') : null;
|
||
const catalogReviewEndpoint = "{{ url_for('market_intel.market_intel_migration_catalog_review') }}?execute=false";
|
||
const liveSmokeMeta = liveSmokeRoot ? liveSmokeRoot.querySelector('[data-market-intel-live-smoke-meta]') : null;
|
||
const liveSmokeBody = liveSmokeRoot ? liveSmokeRoot.querySelector('[data-market-intel-live-smoke-body]') : null;
|
||
const liveSmokeRefresh = liveSmokeRoot ? liveSmokeRoot.querySelector('[data-market-intel-live-smoke-refresh]') : null;
|
||
const liveSmokeEndpoint = "{{ url_for('market_intel.market_intel_migration_live_smoke') }}?execute=false";
|
||
const liveInventoryMeta = liveInventoryRoot ? liveInventoryRoot.querySelector('[data-market-intel-live-inventory-meta]') : null;
|
||
const liveInventoryBody = liveInventoryRoot ? liveInventoryRoot.querySelector('[data-market-intel-live-inventory-body]') : null;
|
||
const liveInventoryRefresh = liveInventoryRoot ? liveInventoryRoot.querySelector('[data-market-intel-live-inventory-refresh]') : null;
|
||
const liveInventoryEndpoint = "{{ url_for('market_intel.market_intel_live_db_inventory') }}?execute=false";
|
||
const approvalMeta = approvalRoot ? approvalRoot.querySelector('[data-market-intel-approval-meta]') : null;
|
||
const approvalBody = approvalRoot ? approvalRoot.querySelector('[data-market-intel-approval-body]') : null;
|
||
const approvalRefresh = approvalRoot ? approvalRoot.querySelector('[data-market-intel-approval-refresh]') : null;
|
||
const approvalEndpoint = "{{ url_for('market_intel.market_intel_write_approval_runbook') }}";
|
||
const deployMeta = deployRoot ? deployRoot.querySelector('[data-market-intel-deploy-meta]') : null;
|
||
const deployBody = deployRoot ? deployRoot.querySelector('[data-market-intel-deploy-body]') : null;
|
||
const deployRefresh = deployRoot ? deployRoot.querySelector('[data-market-intel-deploy-refresh]') : null;
|
||
const deployEndpoint = "{{ url_for('market_intel.market_intel_deployment_readiness') }}";
|
||
|
||
const escapeHtml = value => String(value == null ? '' : value)
|
||
.replace(/&/g, '&')
|
||
.replace(/</g, '<')
|
||
.replace(/>/g, '>')
|
||
.replace(/"/g, '"')
|
||
.replace(/'/g, ''');
|
||
|
||
const renderMeta = data => {
|
||
meta.innerHTML = [
|
||
`candidates=${data.candidate_count || 0}`,
|
||
`fetch=${data.fetch_requested ? 'true' : 'false'}`,
|
||
`manual=${data.manual_fetch_allowed ? 'on' : 'off'}`,
|
||
`db_write=${data.database_write_allowed ? 'on' : 'off'}`,
|
||
`scheduler=${data.scheduler_attached ? 'on' : 'off'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderBody = data => {
|
||
if (!data.candidates || data.candidates.length === 0) {
|
||
const status = (data.run_statuses || []).map(item => `${item.platform_code}:${item.status}`).join(' / ');
|
||
body.innerHTML = `<div class="market-intel-empty">目前沒有候選連結。${status ? `狀態:${escapeHtml(status)}` : ''}</div>`;
|
||
return;
|
||
}
|
||
|
||
body.innerHTML = `<div class="market-intel-candidate-list">${
|
||
data.candidates.map(item => `
|
||
<article class="market-intel-candidate">
|
||
<a href="${escapeHtml(item.href)}" target="_blank" rel="noopener noreferrer">${escapeHtml(item.text || item.href)}</a>
|
||
<small>${escapeHtml(item.platform_code)} / ${escapeHtml(item.confidence_band)} / score=${escapeHtml(item.score)}</small>
|
||
</article>
|
||
`).join('')
|
||
}</div>`;
|
||
};
|
||
|
||
const loadPreview = async () => {
|
||
if (!meta || !body) return;
|
||
body.innerHTML = '<div class="market-intel-empty">讀取候選預覽中...</div>';
|
||
try {
|
||
const response = await fetch(endpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderMeta(data);
|
||
renderBody(data);
|
||
} catch (error) {
|
||
meta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
body.innerHTML = `<div class="market-intel-empty">候選預覽讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderWriterMeta = data => {
|
||
writerMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`operations=${data.operation_count || 0}`,
|
||
`schema=${data.schema_smoke && data.schema_smoke.passed ? 'pass' : 'fail'}`,
|
||
`writes=${data.writes_executed ? 'executed' : 'blocked'}`,
|
||
`db_write=${data.database_write_allowed ? 'on' : 'off'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderWriterBody = data => {
|
||
const reasons = (data.blocked_reasons || []).join(' / ');
|
||
const operations = (data.operations || []).slice(0, 6);
|
||
if (operations.length === 0) {
|
||
writerBody.innerHTML = `<div class="market-intel-empty">沒有 upsert 預覽。${reasons ? `阻擋:${escapeHtml(reasons)}` : ''}</div>`;
|
||
return;
|
||
}
|
||
|
||
writerBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">正式寫入仍被阻擋。${reasons ? `阻擋:${escapeHtml(reasons)}` : ''}</div>
|
||
<div class="market-intel-operation-list">${
|
||
operations.map(item => `
|
||
<article class="market-intel-operation">
|
||
<strong>${escapeHtml(item.lookup && item.lookup.code ? item.lookup.code : item.table)}</strong>
|
||
<small>${escapeHtml(item.operation)} / ${escapeHtml(item.table)} / ${escapeHtml(item.write_status)}</small>
|
||
</article>
|
||
`).join('')
|
||
}</div>
|
||
`;
|
||
};
|
||
|
||
const loadWriter = async () => {
|
||
if (!writerMeta || !writerBody) return;
|
||
writerBody.innerHTML = '<div class="market-intel-empty">讀取寫入預覽中...</div>';
|
||
try {
|
||
const response = await fetch(writerEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderWriterMeta(data);
|
||
renderWriterBody(data);
|
||
} catch (error) {
|
||
writerMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
writerBody.innerHTML = `<div class="market-intel-empty">寫入預覽讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCliMeta = data => {
|
||
const preview = data.transaction_preview || {};
|
||
cliMeta.innerHTML = [
|
||
`mode=${preview.mode || data.mode || 'unknown'}`,
|
||
`exit=${data.exit_code == null ? 'n/a' : data.exit_code}`,
|
||
`statements=${preview.statement_count || 0}`,
|
||
`session=${data.database_session_created ? 'yes' : 'no'}`,
|
||
`commit=${data.database_commit_executed ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderCliBody = data => {
|
||
const preview = data.transaction_preview || {};
|
||
const statements = (preview.statements || []).slice(0, 6);
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
if (!statements.length) {
|
||
cliBody.innerHTML = `<div class="market-intel-empty">沒有交易預覽。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>`;
|
||
return;
|
||
}
|
||
|
||
cliBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">只產生 transaction preview;沒有 DB session、沒有 transaction、沒有 commit。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-operation-list">${
|
||
statements.map(item => `
|
||
<article class="market-intel-operation">
|
||
<strong>${escapeHtml(item.idempotency_key || item.table)}</strong>
|
||
<small>${escapeHtml(item.operation)} / ${escapeHtml(item.table)} / ${escapeHtml(item.diff_status)} / hash=${escapeHtml(item.parameter_payload_hash)}</small>
|
||
</article>
|
||
`).join('')
|
||
}</div>
|
||
`;
|
||
};
|
||
|
||
const loadCli = async () => {
|
||
if (!cliMeta || !cliBody) return;
|
||
cliBody.innerHTML = '<div class="market-intel-empty">讀取交易預覽中...</div>';
|
||
try {
|
||
const response = await fetch(cliEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderCliMeta(data);
|
||
renderCliBody(data);
|
||
} catch (error) {
|
||
cliMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
cliBody.innerHTML = `<div class="market-intel-empty">交易預覽讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderDbProbeMeta = data => {
|
||
dbProbeMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`execute=${data.execute_requested ? 'true' : 'false'}`,
|
||
`query=${data.read_only_query_executed ? 'yes' : 'no'}`,
|
||
`tables=${(data.expected_tables || []).length}`,
|
||
`missing=${(data.missing_tables || []).length}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderDbProbeBody = data => {
|
||
const statuses = (data.table_statuses || []).slice(0, 8);
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
dbProbeBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">預設只顯示 planned,不自動查正式 DB;人工 smoke 時才可明確開啟只讀查詢參數。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-check-list">${
|
||
statuses.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.table)}</strong>
|
||
<small>${data.read_only_query_executed ? 'read_only_catalog_query' : 'planned_no_db_connection'}</small>
|
||
</div>
|
||
<span>${item.exists ? 'EXISTS' : 'PENDING'}</span>
|
||
</div>
|
||
`).join('')
|
||
}</div>
|
||
`;
|
||
};
|
||
|
||
const loadDbProbe = async () => {
|
||
if (!dbProbeMeta || !dbProbeBody) return;
|
||
dbProbeBody.innerHTML = '<div class="market-intel-empty">讀取 DB 探針中...</div>';
|
||
try {
|
||
const response = await fetch(dbProbeEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderDbProbeMeta(data);
|
||
renderDbProbeBody(data);
|
||
} catch (error) {
|
||
dbProbeMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
dbProbeBody.innerHTML = `<div class="market-intel-empty">DB 探針讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderSeedDiffMeta = data => {
|
||
seedDiffMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`execute=${data.execute_requested ? 'true' : 'false'}`,
|
||
`query=${data.read_only_query_executed ? 'yes' : 'no'}`,
|
||
`expected=${data.expected_seed_count || 0}`,
|
||
`missing=${(data.missing_codes || []).length}`,
|
||
`changed=${(data.changed_codes || []).length}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderSeedDiffBody = data => {
|
||
const diffs = (data.seed_diffs || []).slice(0, 8);
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
seedDiffBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">預設只顯示 seed 差異 planned,不自動查正式 DB;人工 smoke 時才可明確開啟只讀查詢參數。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-check-list">${
|
||
diffs.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.code)}</strong>
|
||
<small>${escapeHtml(item.diff_status || 'unknown')}</small>
|
||
</div>
|
||
<span>${item.exists ? 'EXISTS' : 'PENDING'}</span>
|
||
</div>
|
||
`).join('')
|
||
}</div>
|
||
`;
|
||
};
|
||
|
||
const loadSeedDiff = async () => {
|
||
if (!seedDiffMeta || !seedDiffBody) return;
|
||
seedDiffBody.innerHTML = '<div class="market-intel-empty">讀取 Seed 差異探針中...</div>';
|
||
try {
|
||
const response = await fetch(seedDiffEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderSeedDiffMeta(data);
|
||
renderSeedDiffBody(data);
|
||
} catch (error) {
|
||
seedDiffMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
seedDiffBody.innerHTML = `<div class="market-intel-empty">Seed 差異探針讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderLegacyBridgeMeta = data => {
|
||
legacyBridgeMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`execute=${data.execute_requested ? 'true' : 'false'}`,
|
||
`query=${data.read_only_query_executed ? 'yes' : 'no'}`,
|
||
`sources=${data.existing_source_count || 0}/${data.source_count || 0}`,
|
||
`rows=${data.total_existing_rows || 0}`,
|
||
`writes=${data.writes_executed ? 'executed' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderLegacyBridgeBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const sources = data.source_summaries || [];
|
||
const operations = data.bridge_operations || [];
|
||
const controls = data.duplicate_controls || [];
|
||
const renderSource = item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.table)}</strong>
|
||
<small>${escapeHtml(item.description || '')}</small>
|
||
<small>target=${escapeHtml((item.planned_targets || []).join(' / '))}</small>
|
||
</div>
|
||
<span>${item.exists ? escapeHtml(item.row_count || 0) : 'PENDING'}</span>
|
||
</div>
|
||
`;
|
||
const renderOperation = item => `
|
||
<article class="market-intel-operation">
|
||
<strong>${escapeHtml(item.source_table)} → ${escapeHtml(item.target_table)}</strong>
|
||
<small>${escapeHtml(item.operation)} / ${escapeHtml(item.write_status)}</small>
|
||
<small>dedupe=${escapeHtml(item.dedupe_key)}</small>
|
||
</article>
|
||
`;
|
||
const renderControl = item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.rule)}</small>
|
||
</div>
|
||
<span>RULE</span>
|
||
</div>
|
||
`;
|
||
|
||
legacyBridgeBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">這裡只盤點既有資料來源與導入規則,不搬資料、不寫 market_*。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-legacy-sources>
|
||
<p class="market-intel-deploy-section-title">SOURCE TABLES</p>
|
||
<div class="market-intel-check-list">${
|
||
sources.length
|
||
? sources.map(renderSource).join('')
|
||
: '<div class="market-intel-empty">尚未提供來源摘要。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-legacy-operations>
|
||
<p class="market-intel-deploy-section-title">BRIDGE OPERATIONS</p>
|
||
<div class="market-intel-operation-list">${
|
||
operations.length
|
||
? operations.map(renderOperation).join('')
|
||
: '<div class="market-intel-empty">尚未提供橋接操作。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-legacy-controls>
|
||
<p class="market-intel-deploy-section-title">DUPLICATE CONTROLS</p>
|
||
<div class="market-intel-check-list">${
|
||
controls.length
|
||
? controls.map(renderControl).join('')
|
||
: '<div class="market-intel-empty">尚未提供去重規則。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadLegacyBridge = async () => {
|
||
if (!legacyBridgeMeta || !legacyBridgeBody) return;
|
||
legacyBridgeBody.innerHTML = '<div class="market-intel-empty">讀取既有資料橋接預覽中...</div>';
|
||
try {
|
||
const response = await fetch(legacyBridgeEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderLegacyBridgeMeta(data);
|
||
renderLegacyBridgeBody(data);
|
||
} catch (error) {
|
||
legacyBridgeMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
legacyBridgeBody.innerHTML = `<div class="market-intel-empty">既有資料橋接預覽讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderMcpReadinessMeta = data => {
|
||
mcpReadinessMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`router=${data.router_enabled ? 'on' : 'off'}`,
|
||
`external=${data.external_mcp_complete ? 'complete' : 'pending'}`,
|
||
`internal=${data.internal_mcp_complete ? 'complete' : 'pending'}`,
|
||
`market_tools=${data.market_intel_tool_count || 0}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderMcpReadinessBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const servers = data.server_statuses || [];
|
||
const checks = Object.entries(data.readiness_checks || {});
|
||
const expectedTools = data.expected_market_intel_tools || [];
|
||
const registeredCallers = data.registered_callers || [];
|
||
const toolContract = data.mcp_tool_contract || {};
|
||
const contractTools = toolContract.tools || [];
|
||
const telemetry = data.telemetry || {};
|
||
mcpReadinessBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">目前只做 MCP readiness planned preview;不自動呼叫外部平台、不建立 DB session、不寫入 telemetry。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-mcp-servers>
|
||
<p class="market-intel-deploy-section-title">EXTERNAL MCP SERVERS</p>
|
||
<div class="market-intel-check-list">${
|
||
servers.length
|
||
? servers.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.server)}</strong>
|
||
<small>${escapeHtml(item.base_url || 'not configured')}</small>
|
||
<small>${escapeHtml(item.status || '')}</small>
|
||
</div>
|
||
<span>${item.healthy ? 'HEALTHY' : item.configured ? 'PENDING' : 'MISSING'}</span>
|
||
</div>
|
||
`).join('')
|
||
: '<div class="market-intel-empty">尚未提供 MCP server 狀態。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-checks>
|
||
<p class="market-intel-deploy-section-title">READINESS CHECKS</p>
|
||
<div class="market-intel-check-list">${
|
||
checks.length
|
||
? checks.map(([name, passed]) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(name)}</strong>
|
||
</div>
|
||
<span>${passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('')
|
||
: '<div class="market-intel-empty">尚未提供 readiness checks。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-tools>
|
||
<p class="market-intel-deploy-section-title">INTERNAL TOOL CONTRACT</p>
|
||
<div class="market-intel-check-list">
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>registered_callers</strong>
|
||
<small>${escapeHtml(registeredCallers.join(' / ') || 'none')}</small>
|
||
</div>
|
||
<span>${registeredCallers.length}</span>
|
||
</div>
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>market_intel_contract</strong>
|
||
<small>${escapeHtml((toolContract.blocked_reasons || []).join(' / ') || 'ready')}</small>
|
||
</div>
|
||
<span>${toolContract.contract_ready ? 'READY' : 'PENDING'}</span>
|
||
</div>
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>expected_tool_names</strong>
|
||
<small>${escapeHtml(expectedTools.join(' / ') || 'none')}</small>
|
||
</div>
|
||
<span>${escapeHtml(data.market_intel_tool_count || 0)}</span>
|
||
</div>
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>mcp_calls telemetry</strong>
|
||
<small>${escapeHtml(telemetry.mode || 'unknown')} / table=${telemetry.table_exists ? 'exists' : 'pending'}</small>
|
||
</div>
|
||
<span>${telemetry.read_only_query_executed ? 'QUERY' : 'PLANNED'}</span>
|
||
</div>
|
||
${contractTools.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.name)}</strong>
|
||
<small>${escapeHtml(item.server)} / ${escapeHtml((item.router_tools || []).join(' + '))}</small>
|
||
</div>
|
||
<span>${item.router_tools_whitelisted ? 'ALLOW' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadMcpReadiness = async () => {
|
||
if (!mcpReadinessMeta || !mcpReadinessBody) return;
|
||
mcpReadinessBody.innerHTML = '<div class="market-intel-empty">讀取 MCP 整合就緒度中...</div>';
|
||
try {
|
||
const response = await fetch(mcpReadinessEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderMcpReadinessMeta(data);
|
||
renderMcpReadinessBody(data);
|
||
} catch (error) {
|
||
mcpReadinessMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpReadinessBody.innerHTML = `<div class="market-intel-empty">MCP 就緒度讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderMcpPreflightMeta = data => {
|
||
const missingRequiredEnv = (data.env_statuses || []).filter(item => item.required && !item.present).length;
|
||
mcpPreflightMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`ready=${data.ready_to_start_stack ? 'yes' : 'no'}`,
|
||
`compose=${data.compose_file_present ? 'present' : 'missing'}`,
|
||
`missing_env=${missingRequiredEnv}`,
|
||
`docker=${data.docker_command_executed ? 'executed' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderMcpPreflightBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const envs = data.env_statuses || [];
|
||
const services = data.service_statuses || [];
|
||
const ports = data.port_statuses || [];
|
||
const fallback = data.fallback_plan || [];
|
||
const renderCheck = (key, label, status) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(key)}</strong>
|
||
<small>${escapeHtml(label || '')}</small>
|
||
</div>
|
||
<span>${escapeHtml(status)}</span>
|
||
</div>
|
||
`;
|
||
|
||
mcpPreflightBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">這裡只做外部 MCP 部署預檢,不執行 docker、SSH、DB role 建立或 crawler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-mcp-preflight-env>
|
||
<p class="market-intel-deploy-section-title">ENV GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
envs.map(item => renderCheck(
|
||
item.name,
|
||
item.required ? 'required' : 'optional',
|
||
item.present ? 'SET' : 'MISSING'
|
||
)).join('')
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-preflight-services>
|
||
<p class="market-intel-deploy-section-title">COMPOSE SERVICES</p>
|
||
<div class="market-intel-check-list">${
|
||
services.map(item => renderCheck(
|
||
item.service,
|
||
data.compose_path || '',
|
||
item.declared ? 'DECLARED' : 'MISSING'
|
||
)).join('')
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-preflight-ports>
|
||
<p class="market-intel-deploy-section-title">LOCALHOST PORTS</p>
|
||
<div class="market-intel-check-list">${
|
||
ports.map(item => renderCheck(
|
||
`localhost:${item.port}`,
|
||
item.localhost_only ? '127.0.0.1 only' : 'not localhost only',
|
||
item.localhost_only ? 'SAFE' : 'BLOCK'
|
||
)).join('')
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-preflight-fallback>
|
||
<p class="market-intel-deploy-section-title">FALLBACK</p>
|
||
<div class="market-intel-check-list">${
|
||
fallback.map(item => renderCheck(
|
||
item.key,
|
||
item.label,
|
||
'READY'
|
||
)).join('')
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadMcpPreflight = async () => {
|
||
if (!mcpPreflightMeta || !mcpPreflightBody) return;
|
||
mcpPreflightBody.innerHTML = '<div class="market-intel-empty">讀取 MCP 部署預檢中...</div>';
|
||
try {
|
||
const response = await fetch(mcpPreflightEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderMcpPreflightMeta(data);
|
||
renderMcpPreflightBody(data);
|
||
} catch (error) {
|
||
mcpPreflightMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpPreflightBody.innerHTML = `<div class="market-intel-empty">MCP 部署預檢讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderMcpActivationMeta = data => {
|
||
mcpActivationMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`ready=${data.ready_for_operator_activation ? 'yes' : 'no'}`,
|
||
`stages=${(data.stages || []).length}`,
|
||
`blocked=${(data.blocked_reasons || []).length}`,
|
||
`docker=${data.docker_command_executed ? 'executed' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderMcpActivationBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const stages = data.stages || [];
|
||
const fallback = data.fallback_plan || [];
|
||
const safety = Object.entries(data.safety_contract || {});
|
||
const renderStage = item => `
|
||
<article class="market-intel-operation">
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
<small>gate=${escapeHtml(item.gate)} / status=${escapeHtml(item.status)}</small>
|
||
${item.command_preview ? `<small>${escapeHtml(item.command_preview)}</small>` : ''}
|
||
${item.notes ? `<small>${escapeHtml(item.notes)}</small>` : ''}
|
||
</article>
|
||
`;
|
||
const renderCheck = (key, label, status) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(key)}</strong>
|
||
<small>${escapeHtml(label || '')}</small>
|
||
</div>
|
||
<span>${escapeHtml(status)}</span>
|
||
</div>
|
||
`;
|
||
|
||
mcpActivationBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">這是 MCP 啟用順序預覽;不寫 env、不執行 docker/SSH、不建立 DB role、不開 router。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-mcp-activation-stages>
|
||
<p class="market-intel-deploy-section-title">ACTIVATION STAGES</p>
|
||
<div class="market-intel-operation-list">${
|
||
stages.length
|
||
? stages.map(renderStage).join('')
|
||
: '<div class="market-intel-empty">尚未提供啟用步驟。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-activation-safety>
|
||
<p class="market-intel-deploy-section-title">SAFETY CONTRACT</p>
|
||
<div class="market-intel-check-list">${
|
||
safety.length
|
||
? safety.map(([key, value]) => renderCheck(key, '', value ? 'YES' : 'NO')).join('')
|
||
: '<div class="market-intel-empty">尚未提供安全合約。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-activation-fallback>
|
||
<p class="market-intel-deploy-section-title">FALLBACK</p>
|
||
<div class="market-intel-check-list">${
|
||
fallback.length
|
||
? fallback.map(item => renderCheck(item.key, item.label, 'READY')).join('')
|
||
: '<div class="market-intel-empty">尚未提供備援方案。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadMcpActivation = async () => {
|
||
if (!mcpActivationMeta || !mcpActivationBody) return;
|
||
mcpActivationBody.innerHTML = '<div class="market-intel-empty">讀取 MCP 啟用 Runbook 中...</div>';
|
||
try {
|
||
const response = await fetch(mcpActivationEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderMcpActivationMeta(data);
|
||
renderMcpActivationBody(data);
|
||
} catch (error) {
|
||
mcpActivationMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpActivationBody.innerHTML = `<div class="market-intel-empty">MCP 啟用 Runbook 讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderMcpFetchGateMeta = data => {
|
||
mcpFetchGateMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`fetch=${data.fetch_requested ? 'true' : 'false'}`,
|
||
`gate=${data.manual_fetch_gate_open ? 'open' : 'blocked'}`,
|
||
`network=${data.network_request_allowed ? 'allow' : 'block'}`,
|
||
`blocked=${(data.blocked_reasons || []).length}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderMcpFetchGateBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const checks = Object.entries(data.gate_checks || {});
|
||
const sequence = data.required_sequence || [];
|
||
const readiness = data.mcp_readiness_summary || {};
|
||
mcpFetchGateBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">人工 fetch 目前先由 MCP gate 接管;頁面預設只做 planned preview,不抓外站、不寫 DB、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-mcp-fetch-gate-checks>
|
||
<p class="market-intel-deploy-section-title">FETCH GATE CHECKS</p>
|
||
<div class="market-intel-check-list">${
|
||
checks.length
|
||
? checks.map(([name, passed]) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(name)}</strong>
|
||
</div>
|
||
<span>${passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('')
|
||
: '<div class="market-intel-empty">尚未提供 fetch gate checks。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-gate-sequence>
|
||
<p class="market-intel-deploy-section-title">REQUIRED SEQUENCE</p>
|
||
<div class="market-intel-check-list">${
|
||
sequence.length
|
||
? sequence.map((item, index) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(`step_${index + 1}`)}</strong>
|
||
<small>${escapeHtml(item)}</small>
|
||
</div>
|
||
<span>REQUIRED</span>
|
||
</div>
|
||
`).join('')
|
||
: '<div class="market-intel-empty">尚未提供啟用順序。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-gate-readiness>
|
||
<p class="market-intel-deploy-section-title">READINESS SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>router</strong>
|
||
<small>${escapeHtml(readiness.mode || 'unknown')}</small>
|
||
</div>
|
||
<span>${readiness.router_enabled ? 'ON' : 'OFF'}</span>
|
||
</div>
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>external_mcp</strong>
|
||
<small>${escapeHtml((readiness.blocked_reasons || []).join(' / ') || 'ready')}</small>
|
||
</div>
|
||
<span>${readiness.external_mcp_complete ? 'READY' : 'PENDING'}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadMcpFetchGate = async () => {
|
||
if (!mcpFetchGateMeta || !mcpFetchGateBody) return;
|
||
mcpFetchGateBody.innerHTML = '<div class="market-intel-empty">讀取人工 Fetch 安全閘門中...</div>';
|
||
try {
|
||
const response = await fetch(mcpFetchGateEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderMcpFetchGateMeta(data);
|
||
renderMcpFetchGateBody(data);
|
||
} catch (error) {
|
||
mcpFetchGateMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpFetchGateBody.innerHTML = `<div class="market-intel-empty">人工 Fetch 安全閘門讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderManualSampleMeta = data => {
|
||
manualSampleMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`ready=${data.ready_for_manual_sample_fetch ? 'yes' : 'no'}`,
|
||
`platforms=${data.platform_count || 0}`,
|
||
`sources=${data.sample_source_total || 0}`,
|
||
`network=${data.external_network_executed ? 'executed' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderManualSampleBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const checks = Object.entries(data.gate_checks || {});
|
||
const platforms = data.sample_platforms || [];
|
||
const sequence = data.operator_sequence || [];
|
||
const fallback = data.fallback_plan || [];
|
||
const boundaries = data.safe_boundaries || [];
|
||
const renderCheck = ([name, passed]) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(name)}</strong>
|
||
</div>
|
||
<span>${passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`;
|
||
const renderNamedItem = (item, status) => {
|
||
const normalized = typeof item === 'string' ? { key: item, label: item } : (item || {});
|
||
return `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(normalized.key || normalized.label || 'item')}</strong>
|
||
<small>${escapeHtml(normalized.label || normalized.key || '')}</small>
|
||
</div>
|
||
<span>${escapeHtml(normalized.status || status || 'required').toUpperCase()}</span>
|
||
</div>
|
||
`;
|
||
};
|
||
const renderPlatform = item => {
|
||
const firstSource = (item.selected_sources || [])[0] || {};
|
||
return `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.priority)}. ${escapeHtml(item.platform_code)}</strong>
|
||
<small>${escapeHtml(firstSource.name || item.platform_name)} / interval=${escapeHtml(item.request_interval_sec)}s / timeout=${escapeHtml(item.timeout_sec)}s</small>
|
||
</div>
|
||
<span>${escapeHtml(item.network_status || 'not_executed').toUpperCase()}</span>
|
||
</div>
|
||
`;
|
||
};
|
||
manualSampleBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只規劃第一次人工樣本 fetch;API/UI 不抓外站、不寫 DB、不建立 crawler run、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-manual-sample-checks>
|
||
<p class="market-intel-deploy-section-title">SAMPLE GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
checks.length
|
||
? checks.map(renderCheck).join('')
|
||
: '<div class="market-intel-empty">尚未提供 sample gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-manual-sample-platforms>
|
||
<p class="market-intel-deploy-section-title">SAMPLE PLATFORMS</p>
|
||
<div class="market-intel-check-list">${
|
||
platforms.length
|
||
? platforms.map(renderPlatform).join('')
|
||
: '<div class="market-intel-empty">尚未提供平台樣本。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-manual-sample-sequence>
|
||
<p class="market-intel-deploy-section-title">OPERATOR SEQUENCE</p>
|
||
<div class="market-intel-check-list">${
|
||
sequence.length
|
||
? sequence.map(item => renderNamedItem(item, 'required')).join('')
|
||
: '<div class="market-intel-empty">尚未提供人工順序。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-manual-sample-fallback>
|
||
<p class="market-intel-deploy-section-title">FALLBACK</p>
|
||
<div class="market-intel-check-list">${
|
||
fallback.length
|
||
? fallback.map(item => renderNamedItem(item, 'ready')).join('')
|
||
: '<div class="market-intel-empty">尚未提供備援方案。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-manual-sample-boundaries>
|
||
<p class="market-intel-deploy-section-title">BOUNDARIES</p>
|
||
<div class="market-intel-check-list">${
|
||
boundaries.length
|
||
? boundaries.map(item => renderNamedItem(item, 'required')).join('')
|
||
: '<div class="market-intel-empty">尚未提供安全邊界。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadManualSample = async () => {
|
||
if (!manualSampleMeta || !manualSampleBody) return;
|
||
manualSampleBody.innerHTML = '<div class="market-intel-empty">讀取人工樣本 Fetch 計畫中...</div>';
|
||
try {
|
||
const response = await fetch(manualSampleEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderManualSampleMeta(data);
|
||
renderManualSampleBody(data);
|
||
} catch (error) {
|
||
manualSampleMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
manualSampleBody.innerHTML = `<div class="market-intel-empty">人工樣本 Fetch 計畫讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderSampleAcceptanceMeta = data => {
|
||
sampleAcceptanceMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`contract=${data.contract_ready ? 'ready' : 'blocked'}`,
|
||
`loaded=${data.sample_result_loaded ? 'yes' : 'no'}`,
|
||
`accepted=${data.sample_result_accepted ? 'yes' : 'no'}`,
|
||
`import=${data.candidate_import_allowed ? 'allow' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderSampleAcceptanceBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const checks = Object.entries(data.gate_checks || {});
|
||
const acceptance = data.acceptance_checks || [];
|
||
const reject = data.reject_conditions || [];
|
||
const decisions = data.operator_decisions || [];
|
||
const sequence = data.promotion_sequence || [];
|
||
const boundaries = data.safe_boundaries || [];
|
||
const thresholds = data.acceptance_thresholds || {};
|
||
const renderCheck = ([name, passed]) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(name)}</strong>
|
||
</div>
|
||
<span>${passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`;
|
||
const renderNamedItem = (item, status) => {
|
||
const normalized = typeof item === 'string' ? { key: item, label: item } : (item || {});
|
||
return `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(normalized.key || normalized.label || 'item')}</strong>
|
||
<small>${escapeHtml(normalized.label || normalized.key || '')}</small>
|
||
</div>
|
||
<span>${escapeHtml(normalized.status || status || 'required').toUpperCase()}</span>
|
||
</div>
|
||
`;
|
||
};
|
||
sampleAcceptanceBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只定義 sample fetch 回來後的驗收契約;不載入 sample result、不抓外站、不寫 DB、不建立候選活動。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-empty mb-3">required_fields=${escapeHtml((data.required_result_fields || []).length)} / diagnostics=${escapeHtml((data.required_diagnostic_fields || []).length)} / min_candidates=${escapeHtml(thresholds.minimum_campaign_candidates || 0)} / bands=${escapeHtml((thresholds.accepted_candidate_bands || []).join('/'))}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-sample-acceptance-checks>
|
||
<p class="market-intel-deploy-section-title">CONTRACT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
checks.length
|
||
? checks.map(renderCheck).join('')
|
||
: '<div class="market-intel-empty">尚未提供 contract gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-sample-acceptance-fields>
|
||
<p class="market-intel-deploy-section-title">REQUIRED FIELDS</p>
|
||
<div class="market-intel-check-list">${
|
||
(data.required_result_fields || []).map(item => renderNamedItem(String(item), 'required')).join('')
|
||
|| '<div class="market-intel-empty">尚未提供欄位契約。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-sample-acceptance-rules>
|
||
<p class="market-intel-deploy-section-title">ACCEPTANCE RULES</p>
|
||
<div class="market-intel-check-list">${
|
||
acceptance.length
|
||
? acceptance.map(item => renderNamedItem(item, 'not_evaluated')).join('')
|
||
: '<div class="market-intel-empty">尚未提供驗收規則。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-sample-acceptance-reject>
|
||
<p class="market-intel-deploy-section-title">REJECT CONDITIONS</p>
|
||
<div class="market-intel-check-list">${
|
||
reject.length
|
||
? reject.map(item => renderNamedItem(item, 'reject')).join('')
|
||
: '<div class="market-intel-empty">尚未提供拒收條件。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-sample-acceptance-decisions>
|
||
<p class="market-intel-deploy-section-title">OPERATOR DECISIONS</p>
|
||
<div class="market-intel-check-list">${
|
||
decisions.length
|
||
? decisions.map(item => renderNamedItem(item, item.write_status || 'blocked')).join('')
|
||
: '<div class="market-intel-empty">尚未提供人工決策。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-sample-acceptance-sequence>
|
||
<p class="market-intel-deploy-section-title">PROMOTION SEQUENCE</p>
|
||
<div class="market-intel-check-list">${
|
||
sequence.length
|
||
? sequence.map(item => renderNamedItem(String(item), 'required')).join('')
|
||
: '<div class="market-intel-empty">尚未提供升級順序。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-sample-acceptance-boundaries>
|
||
<p class="market-intel-deploy-section-title">BOUNDARIES</p>
|
||
<div class="market-intel-check-list">${
|
||
boundaries.length
|
||
? boundaries.map(item => renderNamedItem(String(item), 'required')).join('')
|
||
: '<div class="market-intel-empty">尚未提供安全邊界。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadSampleAcceptance = async () => {
|
||
if (!sampleAcceptanceMeta || !sampleAcceptanceBody) return;
|
||
sampleAcceptanceBody.innerHTML = '<div class="market-intel-empty">讀取樣本結果驗收契約中...</div>';
|
||
try {
|
||
const response = await fetch(sampleAcceptanceEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderSampleAcceptanceMeta(data);
|
||
renderSampleAcceptanceBody(data);
|
||
} catch (error) {
|
||
sampleAcceptanceMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleAcceptanceBody.innerHTML = `<div class="market-intel-empty">樣本結果驗收契約讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderSampleReviewMeta = data => {
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`loaded=${data.sample_result_loaded ? 'yes' : 'no'}`,
|
||
`reviewed=${data.sample_result_reviewed ? 'yes' : 'no'}`,
|
||
`result=${data.review_result || 'planned'}`,
|
||
`import=${data.candidate_import_allowed ? 'open' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderSampleReviewBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const checks = data.review_checks || [];
|
||
const findings = data.review_findings || [];
|
||
const candidates = data.candidate_summary || {};
|
||
const actions = data.operator_next_actions || [];
|
||
const boundaries = data.safe_boundaries || [];
|
||
const renderCheck = item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key || 'check')}</strong>
|
||
<small>${escapeHtml(item.label || '')}</small>
|
||
</div>
|
||
<span>${escapeHtml(item.status || 'planned').toUpperCase()}</span>
|
||
</div>
|
||
`;
|
||
const renderNamedItem = (item, status) => {
|
||
const normalized = typeof item === 'string' ? { key: item, label: item } : (item || {});
|
||
return `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(normalized.key || normalized.label || 'item')}</strong>
|
||
<small>${escapeHtml(normalized.label || normalized.key || '')}</small>
|
||
</div>
|
||
<span>${escapeHtml(normalized.severity || normalized.write_status || status || 'blocked').toUpperCase()}</span>
|
||
</div>
|
||
`;
|
||
};
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只用純函式審核人工 sample result;目前預設不載入結果、不抓外站、不存檔、不寫 DB、不建立候選活動。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-empty mb-3">candidate_count=${escapeHtml(candidates.candidate_count || 0)} / accepted=${escapeHtml(candidates.accepted_candidate_count || 0)} / bands=${escapeHtml((candidates.accepted_candidate_bands || []).join('/'))}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-sample-review-checks>
|
||
<p class="market-intel-deploy-section-title">REVIEW CHECKS</p>
|
||
<div class="market-intel-check-list">${
|
||
checks.length
|
||
? checks.map(renderCheck).join('')
|
||
: '<div class="market-intel-empty">尚未載入 sample result,審核檢查維持 planned。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-sample-review-findings>
|
||
<p class="market-intel-deploy-section-title">FINDINGS</p>
|
||
<div class="market-intel-check-list">${
|
||
findings.length
|
||
? findings.map(item => renderNamedItem(item, 'block')).join('')
|
||
: '<div class="market-intel-empty">目前沒有審核發現;尚未載入結果時代表未評估。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-sample-review-actions>
|
||
<p class="market-intel-deploy-section-title">NEXT ACTIONS</p>
|
||
<div class="market-intel-check-list">${
|
||
actions.length
|
||
? actions.map(item => renderNamedItem(item, item.write_status || 'blocked')).join('')
|
||
: '<div class="market-intel-empty">尚未提供下一步。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-sample-review-boundaries>
|
||
<p class="market-intel-deploy-section-title">BOUNDARIES</p>
|
||
<div class="market-intel-check-list">${
|
||
boundaries.length
|
||
? boundaries.map(item => renderNamedItem(String(item), 'required')).join('')
|
||
: '<div class="market-intel-empty">尚未提供安全邊界。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadSampleReview = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody) return;
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">讀取樣本結果審核預覽中...</div>';
|
||
try {
|
||
const response = await fetch(sampleReviewEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderSampleReviewMeta(data);
|
||
renderSampleReviewBody(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">樣本結果審核預覽讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const evaluateSampleReview = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">審核 sample result 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleReviewEvaluateEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify({ sample_result: parsed })
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderSampleReviewMeta(data);
|
||
renderSampleReviewBody(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">樣本結果審核失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateHandoff = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const summary = data.handoff_summary || {};
|
||
const candidates = data.candidates || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`handoff=${data.handoff_ready ? 'ready' : 'blocked'}`,
|
||
`candidates=${summary.candidate_count || 0}`,
|
||
`persisted=${data.candidate_handoff_persisted ? 'yes' : 'no'}`,
|
||
`import=${data.candidate_import_allowed ? 'open' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只產生候選活動 handoff preview;不保存 payload、不建立活動、不寫 market_*。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-operation-list">${
|
||
candidates.length
|
||
? candidates.map(item => `
|
||
<article class="market-intel-operation">
|
||
<strong>${escapeHtml(item.candidate_text || item.candidate_url)}</strong>
|
||
<small>${escapeHtml(item.platform_code)} / ${escapeHtml(item.confidence_band)} / score=${escapeHtml(item.score)} / ${escapeHtml(item.write_status)}</small>
|
||
<small>${escapeHtml(item.candidate_url)}</small>
|
||
</article>
|
||
`).join('')
|
||
: '<div class="market-intel-empty">目前沒有可交接候選;請先讓 sample result 通過審核。</div>'
|
||
}</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateHandoff = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">產生候選 handoff 預覽中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateHandoffEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify({ sample_result: parsed })
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateHandoff(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">候選 handoff 產生失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueDraft = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const summary = data.queue_summary || {};
|
||
const queueItems = data.queue_items || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`queue=${data.queue_draft_ready ? 'ready' : 'blocked'}`,
|
||
`items=${summary.queue_item_count || 0}`,
|
||
`persisted=${data.review_queue_persisted ? 'yes' : 'no'}`,
|
||
`import=${data.candidate_import_allowed ? 'open' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只產生候選審核 queue 草案;不建立正式 queue、不寫 DB、不自動核准候選。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-operation-list">${
|
||
queueItems.length
|
||
? queueItems.map(item => `
|
||
<article class="market-intel-operation">
|
||
<strong>${escapeHtml(item.candidate_text || item.candidate_url)}</strong>
|
||
<small>${escapeHtml(item.platform_code)} / ${escapeHtml(item.confidence_band)} / score=${escapeHtml(item.score)} / priority=${escapeHtml(item.review_priority)} / ${escapeHtml(item.review_state)}</small>
|
||
<small>${escapeHtml(item.write_status)} / approval=${item.approval_required ? 'required' : 'none'}</small>
|
||
<small>${escapeHtml(item.candidate_url)}</small>
|
||
</article>
|
||
`).join('')
|
||
: '<div class="market-intel-empty">目前沒有可建立草案的候選;請先產生通過審核的 handoff。</div>'
|
||
}</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueDraft = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">產生候選審核 queue 草案中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueDraftEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify({ sample_result: parsed })
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueDraft(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">候選審核 queue 草案產生失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueApproval = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const summary = data.approval_summary || {};
|
||
const gates = data.approval_gates || [];
|
||
const rows = (data.queue_write_preview || {}).rows || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`target=${summary.target_table || 'unknown'}`,
|
||
`rows=${summary.row_preview_count || 0}`,
|
||
`gates=${summary.gates_passed || 0}/${summary.gate_count || 0}`,
|
||
`write=${data.review_queue_write_allowed ? 'open' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只做 queue 寫入送審 gate;不建立 approval record、不寫 market_alert_review_queue、不開 DB transaction。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-check-list mb-3">${
|
||
gates.length
|
||
? gates.map(item => renderNamedItem(item, item.passed ? 'pass' : 'block')).join('')
|
||
: '<div class="market-intel-empty">尚未產生 gate。</div>'
|
||
}</div>
|
||
<div class="market-intel-operation-list">${
|
||
rows.length
|
||
? rows.map(item => `
|
||
<article class="market-intel-operation">
|
||
<strong>${escapeHtml(item.alert_candidate_id)}</strong>
|
||
<small>${escapeHtml(item.review_state)} / ${escapeHtml(item.priority_lane)} / ${escapeHtml(item.threshold_level)} / score=${escapeHtml(item.total_score)}</small>
|
||
<small>${escapeHtml(item.dedupe_key)} / ${escapeHtml(item.write_status)}</small>
|
||
</article>
|
||
`).join('')
|
||
: '<div class="market-intel-empty">目前沒有可送審的 queue row preview。</div>'
|
||
}</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueApproval = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">檢查 queue 寫入送審 gate 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueApprovalEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify({ sample_result: parsed })
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueApproval(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue 寫入送審 gate 檢查失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueTransaction = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const summary = data.transaction_summary || {};
|
||
const statements = data.statements || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`target=${summary.target_table || 'unknown'}`,
|
||
`statements=${summary.statement_count || 0}`,
|
||
`conflict=${summary.conflict_policy || 'none'}`,
|
||
`ready=${data.transaction_ready ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只產生 transaction preview;不開 DB connection、不開 transaction、不 commit、不建立 approval record。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-operation-list">${
|
||
statements.length
|
||
? statements.map(item => `
|
||
<article class="market-intel-operation">
|
||
<strong>${escapeHtml(item.idempotency_key)}</strong>
|
||
<small>${escapeHtml(item.operation)} / ${escapeHtml(item.table)} / ${escapeHtml(item.write_status)}</small>
|
||
<small>${escapeHtml(item.sql_shape)}</small>
|
||
<small>payload=${escapeHtml(item.parameter_payload_hash)}</small>
|
||
</article>
|
||
`).join('')
|
||
: '<div class="market-intel-empty">目前沒有可檢查的 transaction statement。</div>'
|
||
}</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueTransaction = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">產生 queue transaction preview 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueTransactionEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify({ sample_result: parsed })
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueTransaction(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue transaction preview 產生失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueWriter = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const summary = data.transaction_preview_summary || {};
|
||
const gates = data.approval_gates || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`ready=${data.ready_for_real_write ? 'yes' : 'no'}`,
|
||
`statements=${summary.statement_count || 0}`,
|
||
`execute=${data.execute_requested ? 'yes' : 'no'}`,
|
||
`apply=${data.apply_real_write_requested ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只檢查 CLI writer gate;不讀取 token、不開 DB connection、不寫 queue。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
</div>
|
||
`).join('')
|
||
}</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueWriter = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">檢查 queue writer CLI gate 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueWriterEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify({ sample_result: parsed })
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueWriter(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue writer CLI gate 檢查失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueuePreflight = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const columns = data.mapped_insert_columns || [];
|
||
const missing = data.missing_insert_columns || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`schema=${data.schema_ready ? 'ready' : 'blocked'}`,
|
||
`mapped=${columns.length}`,
|
||
`missing=${missing.length}`,
|
||
`dedupe=${data.dedupe_unique_index_present ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡檢查 payload 與 queue table 欄位相容性;頁面預設不連 DB、不寫 queue。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-operation-list">
|
||
<article class="market-intel-operation">
|
||
<strong>mapped insert columns</strong>
|
||
<small>${escapeHtml(columns.join(', ') || 'none')}</small>
|
||
</article>
|
||
<article class="market-intel-operation">
|
||
<strong>missing columns</strong>
|
||
<small>${escapeHtml(missing.join(', ') || 'none')}</small>
|
||
</article>
|
||
<article class="market-intel-operation">
|
||
<strong>unmapped payload keys</strong>
|
||
<small>${escapeHtml((data.unmapped_payload_keys || []).join(', ') || 'none')}</small>
|
||
</article>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueuePreflight = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">檢查 queue writer preflight 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueuePreflightEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify({ sample_result: parsed })
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueuePreflight(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue writer preflight 檢查失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueuePostwriteSmoke = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const found = data.found_dedupe_keys || [];
|
||
const missing = data.missing_dedupe_keys || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`passed=${data.postwrite_smoke_passed ? 'yes' : 'no'}`,
|
||
`expected=${data.expected_dedupe_key_count || 0}`,
|
||
`found=${data.found_count || 0}`,
|
||
`missing=${data.missing_count || 0}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只做 CLI 寫入後的 queue row 只讀驗證;頁面預設不連 DB、不寫 queue。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-operation-list">
|
||
<article class="market-intel-operation">
|
||
<strong>found dedupe keys</strong>
|
||
<small>${escapeHtml(found.join(', ') || 'none')}</small>
|
||
</article>
|
||
<article class="market-intel-operation">
|
||
<strong>missing dedupe keys</strong>
|
||
<small>${escapeHtml(missing.join(', ') || 'none')}</small>
|
||
</article>
|
||
<article class="market-intel-operation">
|
||
<strong>read-only status</strong>
|
||
<small>query=${data.read_only_query_executed ? 'yes' : 'no'} / write=${data.database_write_executed ? 'yes' : 'no'} / commit=${data.database_commit_executed ? 'yes' : 'no'}</small>
|
||
</article>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueuePostwriteSmoke = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">檢查 queue writer post-write smoke 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueuePostwriteSmokeEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify({ sample_result: parsed })
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueuePostwriteSmoke(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue writer post-write smoke 檢查失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueOperatorDrill = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const commands = data.command_sequence || [];
|
||
const gates = data.gates || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`ready=${data.operator_drill_ready ? 'yes' : 'no'}`,
|
||
`commands=${commands.length}`,
|
||
`api_cli=${data.api_executes_cli ? 'yes' : 'no'}`,
|
||
`db_write=${data.database_write_executed ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只產生操作員演練順序;API/UI 不讀 token、不執行 CLI、不寫 DB。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-check-list mb-3">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
</div>
|
||
`).join('')
|
||
}</div>
|
||
<div class="market-intel-operation-list">${
|
||
commands.map(item => `
|
||
<article class="market-intel-operation">
|
||
<strong>${escapeHtml(item.step)}. ${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
<small>${escapeHtml(item.command_shape)}</small>
|
||
<small>db_write=${item.executes_database ? 'yes' : 'no'}</small>
|
||
</article>
|
||
`).join('')
|
||
}</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueOperatorDrill = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">產生 queue writer operator drill 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueOperatorDrillEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify({ sample_result: parsed })
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueOperatorDrill(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue writer operator drill 產生失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueRunPackage = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const manifest = data.payload_manifest || {};
|
||
const artifacts = data.required_artifacts || [];
|
||
const commands = data.command_bundle || [];
|
||
const signoff = data.operator_signoff || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`ready=${data.package_ready ? 'yes' : 'no'}`,
|
||
`payloads=${manifest.payload_count || 0}`,
|
||
`api_file=${data.api_writes_file ? 'yes' : 'no'}`,
|
||
`db_write=${data.database_write_executed ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只整理正式 CLI 小流量寫入前的證據包與命令包;API/UI 不產檔、不讀 token、不執行 CLI、不寫 DB。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-empty mb-3">manifest=${escapeHtml((manifest.manifest_hash || '').slice(0, 16) || 'none')} / dedupe=${escapeHtml((manifest.dedupe_keys || []).join(', ') || 'none')}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">REQUIRED ARTIFACTS</p>
|
||
<div class="market-intel-operation-list">${
|
||
artifacts.map(item => `
|
||
<article class="market-intel-operation">
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
<small>${escapeHtml(item.path_shape)}</small>
|
||
<small>created_by_api=${item.created_by_api ? 'yes' : 'no'}</small>
|
||
</article>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 artifacts。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">COMMAND BUNDLE</p>
|
||
<div class="market-intel-operation-list">${
|
||
commands.map(item => `
|
||
<article class="market-intel-operation">
|
||
<strong>${escapeHtml(item.step)}. ${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.command_shape)}</small>
|
||
<small>db_write=${item.executes_database ? 'yes' : 'no'}</small>
|
||
</article>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 commands。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR SIGNOFF</p>
|
||
<div class="market-intel-check-list">${
|
||
signoff.map((item, index) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(`signoff_${index + 1}`)}</strong>
|
||
<small>${escapeHtml(item)}</small>
|
||
</div>
|
||
<span>REQUIRED</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 signoff。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueRunPackage = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">產生 queue writer run package 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueRunPackageEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify({ sample_result: parsed })
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueRunPackage(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue writer run package 產生失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueRunReadiness = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const evidence = data.operator_evidence_summary || {};
|
||
const packageSummary = data.run_package_summary || {};
|
||
const gates = data.gates || [];
|
||
const steps = data.next_operator_steps || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`cli_ready=${data.ready_for_cli_operator_run ? 'yes' : 'no'}`,
|
||
`api_write=${data.ready_for_api_database_write ? 'yes' : 'no'}`,
|
||
`payloads=${packageSummary.payload_count || 0}`,
|
||
`paths=${evidence.artifact_path_count || 0}`,
|
||
`token_api=${evidence.approval_token_submitted_to_api ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只檢查正式 CLI 小流量寫入前的操作員證據;API/UI 不讀 token、不執行 CLI、不產檔、不寫 DB。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-empty mb-3">manifest=${escapeHtml((packageSummary.manifest_hash || '').slice(0, 16) || 'none')} / artifacts=${escapeHtml(packageSummary.required_artifact_count || 0)} / commands=${escapeHtml(packageSummary.command_count || 0)}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">READINESS GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 readiness gates。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">EVIDENCE SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['reviewed_sample_json_path_recorded', evidence.reviewed_sample_json_path_recorded],
|
||
['backup_artifact_path_recorded', evidence.backup_artifact_path_recorded],
|
||
['preflight_artifact_path_recorded', evidence.preflight_artifact_path_recorded],
|
||
['migration_live_smoke_passed', evidence.migration_live_smoke_passed],
|
||
['operator_acknowledged_shell_only_token', evidence.operator_acknowledged_shell_only_token],
|
||
['approval_token_submitted_to_api', evidence.approval_token_submitted_to_api]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(key)}</strong>
|
||
</div>
|
||
<span>${value ? 'YES' : 'NO'}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">NEXT STEPS</p>
|
||
<div class="market-intel-check-list">${
|
||
steps.map((item, index) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(`step_${index + 1}`)}</strong>
|
||
<small>${escapeHtml(item)}</small>
|
||
</div>
|
||
<span>MANUAL</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供下一步。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueRunReadiness = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
|
||
const body = parsed && parsed.sample_result
|
||
? parsed
|
||
: { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">檢查 queue writer run readiness 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueRunReadinessEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueRunReadiness(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue writer run readiness 檢查失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueRunReceipt = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const writer = data.writer_output_summary || {};
|
||
const smoke = data.postwrite_smoke_summary || {};
|
||
const evidence = data.operator_evidence_summary || {};
|
||
const gates = data.gates || [];
|
||
const steps = data.next_operator_steps || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`receipt=${data.receipt_passed ? 'pass' : 'blocked'}`,
|
||
`writer=${writer.mode || 'missing'}`,
|
||
`smoke=${smoke.postwrite_smoke_passed ? 'pass' : 'blocked'}`,
|
||
`api_write=${data.ready_for_api_database_write ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只審核 CLI 寫入後的 writer output 與 post-write smoke receipt;API/UI 不讀 token、不執行 CLI、不連 DB、不補寫 queue。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-empty mb-3">expected=${escapeHtml((data.expected_dedupe_keys || []).join(', ') || 'none')} / observed=${escapeHtml((writer.observed_dedupe_keys || []).join(', ') || 'none')} / found=${escapeHtml((smoke.found_dedupe_keys || []).join(', ') || 'none')}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RECEIPT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 receipt gates。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">WRITER / SMOKE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['writer_committed', writer.database_commit_executed],
|
||
['writer_dedupe_match', writer.dedupe_keys_match_expected],
|
||
['writer_token_key_detected', writer.approval_token_key_detected],
|
||
['smoke_read_only', smoke.read_only_query_executed],
|
||
['smoke_passed', smoke.postwrite_smoke_passed],
|
||
['smoke_dedupe_match', smoke.dedupe_keys_match_expected]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(key)}</strong>
|
||
</div>
|
||
<span>${value ? 'YES' : 'NO'}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">ARTIFACT EVIDENCE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['writer_output_json_path_recorded', evidence.writer_output_json_path_recorded],
|
||
['postwrite_smoke_json_path_recorded', evidence.postwrite_smoke_json_path_recorded],
|
||
['operator_confirmed_no_token_in_artifacts', evidence.operator_confirmed_no_token_in_artifacts],
|
||
['approval_token_submitted_to_api', evidence.approval_token_submitted_to_api]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(key)}</strong>
|
||
</div>
|
||
<span>${value ? 'YES' : 'NO'}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">NEXT STEPS</p>
|
||
<div class="market-intel-check-list">${
|
||
steps.map((item, index) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(`step_${index + 1}`)}</strong>
|
||
<small>${escapeHtml(item)}</small>
|
||
</div>
|
||
<span>MANUAL</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供下一步。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueRunReceipt = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
|
||
const body = parsed && parsed.sample_result
|
||
? parsed
|
||
: { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">審核 queue writer run receipt 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueRunReceiptEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueRunReceipt(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue writer run receipt 審核失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueRunCloseout = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const receipt = data.receipt_summary || {};
|
||
const closeout = data.operator_closeout_summary || {};
|
||
const promotion = data.promotion_gate || {};
|
||
const gates = data.gates || [];
|
||
const steps = data.next_operator_steps || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`closeout=${data.closeout_passed ? 'pass' : 'blocked'}`,
|
||
`receipt=${receipt.receipt_passed ? 'pass' : 'blocked'}`,
|
||
`next=${promotion.allowed ? 'manual' : 'hold'}`,
|
||
`api_write=${data.ready_for_api_database_write ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只做 run receipt 後的 closeout gate;通過後也只代表可進人工 queue review / read-only inventory,不代表 API/UI 可寫 DB 或掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">CLOSEOUT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 closeout gates。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RECEIPT SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['receipt_passed', receipt.receipt_passed],
|
||
['dedupe_keys', receipt.expected_dedupe_key_count || 0],
|
||
['writer_match', receipt.writer_dedupe_keys_match_expected],
|
||
['smoke_match', receipt.postwrite_smoke_dedupe_keys_match_expected],
|
||
['safe_boundaries_complete', receipt.safe_boundaries_complete]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${typeof value === 'boolean' ? (value ? 'YES' : 'NO') : escapeHtml(value)}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR CLOSEOUT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['closeout_artifact_path_recorded', closeout.closeout_artifact_path_recorded],
|
||
['operator_confirmed_queue_review_next', closeout.operator_confirmed_queue_review_next],
|
||
['operator_confirmed_no_scheduler_attach', closeout.operator_confirmed_no_scheduler_attach],
|
||
['operator_confirmed_no_api_db_write', closeout.operator_confirmed_no_api_db_write],
|
||
['approval_token_submitted_to_api', closeout.approval_token_submitted_to_api]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${value ? 'YES' : 'NO'}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PROMOTION / NEXT</p>
|
||
<div class="market-intel-check-list">
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(promotion.next_manual_phase || 'manual_phase')}</strong>
|
||
<small>requires_operator_approval=${promotion.requires_operator_approval ? 'yes' : 'no'}</small>
|
||
</div>
|
||
<span>${promotion.allowed ? 'ALLOW' : 'HOLD'}</span>
|
||
</div>
|
||
${steps.map((item, index) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(`step_${index + 1}`)}</strong>
|
||
<small>${escapeHtml(item)}</small>
|
||
</div>
|
||
<span>MANUAL</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueRunCloseout = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
|
||
const body = parsed && parsed.sample_result
|
||
? parsed
|
||
: { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">收尾 queue writer run closeout 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueRunCloseoutEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueRunCloseout(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue writer run closeout 收尾失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewHandoff = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const closeout = data.closeout_summary || {};
|
||
const operator = data.operator_handoff_summary || {};
|
||
const contract = data.review_contract || {};
|
||
const gates = data.gates || [];
|
||
const steps = data.next_operator_steps || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`handoff=${data.handoff_ready ? 'ready' : 'blocked'}`,
|
||
`closeout=${closeout.closeout_passed ? 'pass' : 'blocked'}`,
|
||
`expected=${(data.expected_dedupe_keys || []).length}`,
|
||
`api_write=${data.ready_for_api_database_write ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只產生人工 queue review handoff;不查 DB、不更新 review_state、不補寫 queue、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-empty mb-3">expected dedupe keys:${escapeHtml((data.expected_dedupe_keys || []).join(', ') || 'none')}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">HANDOFF GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 handoff gates。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">REVIEW CONTRACT</p>
|
||
<div class="market-intel-check-list">
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>expected_review_state</strong>
|
||
<small>${escapeHtml(contract.expected_review_state || 'needs_review')}</small>
|
||
</div>
|
||
<span>MANUAL</span>
|
||
</div>
|
||
${(contract.required_columns || []).map(item => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(item)}</strong></div>
|
||
<span>FIELD</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['operator_confirmed_queue_review_next', operator.operator_confirmed_queue_review_next],
|
||
['operator_confirmed_no_scheduler_attach', operator.operator_confirmed_no_scheduler_attach],
|
||
['operator_confirmed_no_api_db_write', operator.operator_confirmed_no_api_db_write],
|
||
['approval_token_submitted_to_api', operator.approval_token_submitted_to_api]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${value ? 'YES' : 'NO'}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">NEXT STEPS</p>
|
||
<div class="market-intel-check-list">${
|
||
steps.map((item, index) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(`step_${index + 1}`)}</strong>
|
||
<small>${escapeHtml(item)}</small>
|
||
</div>
|
||
<span>MANUAL</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供下一步。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewHandoff = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
|
||
const body = parsed && parsed.sample_result
|
||
? parsed
|
||
: { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">產生 queue review handoff 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewHandoffEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewHandoff(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review handoff 產生失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewInventory = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const operator = data.operator_inventory_summary || {};
|
||
const table = data.alert_review_queue_table || {};
|
||
const smoke = data.postwrite_smoke_summary || {};
|
||
const live = data.live_inventory_summary || {};
|
||
const gates = data.gates || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`inventory=${data.review_inventory_ready ? 'ready' : 'blocked'}`,
|
||
`read_only=${data.read_only_query_executed ? 'yes' : 'no'}`,
|
||
`found=${(data.found_dedupe_keys || []).length}`,
|
||
`api_update=${data.api_updates_review_state ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只做 queue review inventory 只讀交接檢查;不更新 review_state、不補寫 queue、不讀 token、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-empty mb-3">expected:${escapeHtml((data.expected_dedupe_keys || []).join(', ') || 'none')} / found:${escapeHtml((data.found_dedupe_keys || []).join(', ') || 'none')}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">INVENTORY GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 inventory gates。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">QUEUE TABLE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['table_exists', table.exists],
|
||
['row_count', table.row_count ?? 'n/a'],
|
||
['table_status', table.status || 'not_reported'],
|
||
['postwrite_mode', smoke.mode || 'planned'],
|
||
['smoke_passed', smoke.postwrite_smoke_passed]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">LIVE INVENTORY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['live_mode', live.mode || 'planned'],
|
||
['summary_ready', live.summary_ready],
|
||
['total_rows', live.total_rows ?? 'n/a'],
|
||
['operator_read_only', operator.operator_confirmed_inventory_read_only],
|
||
['approval_token_submitted_to_api', operator.approval_token_submitted_to_api]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">ROWS</p>
|
||
<div class="market-intel-check-list">${
|
||
(data.row_summaries || []).map(row => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(row.dedupe_key || 'unknown')}</strong>
|
||
<small>${escapeHtml(row.priority_lane || 'n/a')} / score=${escapeHtml(String(row.total_score ?? 'n/a'))}</small>
|
||
</div>
|
||
<span>${escapeHtml(row.review_state || 'unknown')}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未執行只讀 row inventory。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewInventory = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">檢查 queue review inventory 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewInventoryEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewInventory(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review inventory 檢查失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewDecision = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const operator = data.operator_decision_summary || {};
|
||
const contract = data.decision_contract || {};
|
||
const gates = data.gates || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`decision=${data.decision_ready ? 'ready' : 'blocked'}`,
|
||
`proposed=${operator.proposed_review_decision || 'none'}`,
|
||
`rows=${(data.decision_rows || []).length}`,
|
||
`api_update=${data.api_updates_review_state ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只產生人工 queue review decision 草案;不更新 review_state、不寫 decision record、不讀 token、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">DECISION GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 decision gates。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">CONTRACT</p>
|
||
<div class="market-intel-check-list">
|
||
<div class="market-intel-check">
|
||
<div><strong>expected_current_state</strong></div>
|
||
<span>${escapeHtml(contract.expected_current_state || 'needs_review')}</span>
|
||
</div>
|
||
${(contract.allowed_next_states || []).map(item => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(item)}</strong></div>
|
||
<span>ALLOW</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['reviewer_id', operator.reviewer_id || 'missing'],
|
||
['decision_notes_present', operator.decision_notes_present],
|
||
['manual_decision_only', operator.operator_confirmed_manual_decision_only],
|
||
['approval_token_submitted_to_api', operator.approval_token_submitted_to_api]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">DECISION ROWS</p>
|
||
<div class="market-intel-check-list">${
|
||
(data.decision_rows || []).map(row => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(row.dedupe_key || 'unknown')}</strong>
|
||
<small>${escapeHtml(row.current_review_state || 'unknown')} -> ${escapeHtml(row.proposed_review_state || 'none')}</small>
|
||
</div>
|
||
<span>${escapeHtml(row.write_status || 'preview')}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未產生 decision rows。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewDecision = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">產生 queue review decision 草案中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewDecisionEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewDecision(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review decision 草案失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewDecisionApproval = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const operator = data.operator_approval_summary || {};
|
||
const summary = data.approval_summary || {};
|
||
const contract = data.approval_contract || {};
|
||
const gates = data.approval_gates || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`approval=${data.approval_ready ? 'ready' : 'blocked'}`,
|
||
`rows=${(data.decision_update_preview || []).length}`,
|
||
`api_update=${data.api_updates_review_state ? 'yes' : 'no'}`,
|
||
`cli_ready=${data.ready_for_cli_decision_writer ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只檢查 queue review decision 批准 gate;不更新 review_state、不寫 decision record、不讀 token、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">APPROVAL GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 approval gates。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">APPROVAL SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['target_table', summary.target_table || data.target_table || 'unknown'],
|
||
['row_preview_count', summary.row_preview_count ?? 0],
|
||
['manual_required', summary.manual_approval_required],
|
||
['api_write_allowed', summary.api_write_allowed],
|
||
['next_stage', contract.next_stage || 'not_ready']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['payload_reviewed', operator.operator_confirmed_decision_payload_reviewed],
|
||
['cli_only_apply', operator.operator_confirmed_decision_apply_requires_cli],
|
||
['not_api_update', operator.operator_confirmed_review_state_update_is_not_api],
|
||
['notes_present', operator.decision_approval_notes_present],
|
||
['approval_token_submitted_to_api', operator.approval_token_submitted_to_api]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">UPDATE PREVIEW</p>
|
||
<div class="market-intel-check-list">${
|
||
(data.decision_update_preview || []).map(row => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(row.dedupe_key || 'unknown')}</strong>
|
||
<small>${escapeHtml(row.expected_current_review_state || 'unknown')} -> ${escapeHtml(row.approved_next_review_state || 'none')}</small>
|
||
</div>
|
||
<span>${escapeHtml(row.write_status || 'preview')}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未產生 update preview。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewDecisionApproval = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">檢查 queue review decision 批准 gate 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewDecisionApprovalEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewDecisionApproval(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review decision 批准 gate 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewDecisionTransaction = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const operator = data.operator_transaction_summary || {};
|
||
const summary = data.transaction_preview_summary || {};
|
||
const contract = data.transaction_contract || {};
|
||
const gates = data.transaction_gates || [];
|
||
const statements = data.statements || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`transaction=${data.transaction_ready ? 'ready' : 'blocked'}`,
|
||
`statements=${summary.statement_count || 0}`,
|
||
`api_update=${data.api_updates_review_state ? 'yes' : 'no'}`,
|
||
`cli_required=${summary.manual_cli_required ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只產生 queue review decision transaction preview;不更新 review_state、不開 DB connection、不讀 token、不執行 CLI、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">TRANSACTION GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 transaction gates。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">CONTRACT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['target_table', summary.target_table || data.target_table || 'unknown'],
|
||
['expected_current_state', contract.expected_current_state || 'needs_review'],
|
||
['next_stage', contract.next_stage || 'not_ready'],
|
||
['api_write_allowed', summary.api_write_allowed]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['payload_reviewed', operator.operator_confirmed_transaction_payload_reviewed],
|
||
['cli_only_transaction', operator.operator_confirmed_cli_only_transaction],
|
||
['not_api_update', operator.operator_confirmed_review_state_update_is_not_api],
|
||
['notes_present', operator.decision_transaction_notes_present],
|
||
['approval_token_submitted_to_api', operator.approval_token_submitted_to_api]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">STATEMENTS</p>
|
||
<div class="market-intel-check-list">${
|
||
statements.map(row => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(row.idempotency_key || row.lookup?.dedupe_key || 'unknown')}</strong>
|
||
<small>${escapeHtml(row.expected_current_review_state || 'unknown')} -> ${escapeHtml(row.next_review_state || 'none')}</small>
|
||
</div>
|
||
<span>${escapeHtml(row.write_status || 'preview')}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未產生 transaction statements。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewDecisionTransaction = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">產生 queue review decision transaction preview 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewDecisionTransactionEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewDecisionTransaction(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review decision transaction preview 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewDecisionPreflight = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const summary = data.statement_summary || {};
|
||
const payloads = summary.review_state_updates || [];
|
||
const contract = data.update_contract || {};
|
||
const catalog = data.catalog_probe_plan || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`payload=${data.preflight_payload_ready ? 'ready' : 'blocked'}`,
|
||
`statements=${summary.statement_count || 0}`,
|
||
`query=${data.read_only_query_executed ? 'yes' : 'no'}`,
|
||
`db_write=${data.database_write_executed ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只檢查 review_state writer preflight;API/UI 不讀 token、不連 DB、不更新 review_state、不 commit、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PREFLIGHT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
(data.preflight_gates || []).map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 preflight gates。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">CONTRACT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['target_table', contract.target_table || data.target_table || 'unknown'],
|
||
['operation', contract.statement_type || data.target_operation || 'unknown'],
|
||
['lookup', contract.lookup || 'dedupe_key'],
|
||
['current_state', contract.expected_current_review_state || 'needs_review'],
|
||
['allowed_next_states', (contract.allowed_next_states || []).join(', ') || 'none']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">CATALOG PLAN</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['mode', catalog.mode || 'planned_only'],
|
||
['required_columns', (catalog.required_columns || []).join(', ') || 'none'],
|
||
['unique_lookup', catalog.required_unique_lookup || 'dedupe_key'],
|
||
['connection_opened', catalog.database_connection_opened],
|
||
['query_executed', catalog.read_only_query_executed]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PAYLOAD</p>
|
||
<div class="market-intel-check-list">${
|
||
payloads.map(row => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(row.dedupe_key || row.idempotency_key || 'unknown')}</strong>
|
||
<small>${escapeHtml(row.expected_current_review_state || 'unknown')} -> ${escapeHtml(row.next_review_state || 'none')}</small>
|
||
</div>
|
||
<span>${escapeHtml(row.operation || 'update')}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 review_state update payload。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">WRITE FLAGS</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['api_updates_review_state', data.api_updates_review_state],
|
||
['review_state_update_executed', data.review_state_update_executed],
|
||
['database_write', data.database_write_executed],
|
||
['scheduler', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewDecisionPreflight = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">檢查 queue review decision writer preflight 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewDecisionPreflightEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewDecisionPreflight(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review decision writer preflight 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewDecisionPostwriteSmoke = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const rows = data.row_summaries || [];
|
||
const mismatches = data.state_mismatches || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`passed=${data.postwrite_smoke_passed ? 'yes' : 'no'}`,
|
||
`expected=${data.expected_dedupe_key_count || 0}`,
|
||
`found=${data.found_count || 0}`,
|
||
`mismatch=${data.state_mismatch_count || 0}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只做 review_state writer post-write 只讀驗證;API/UI 不讀 token、不更新 review_state、不 commit、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">EXPECTED UPDATES</p>
|
||
<div class="market-intel-check-list">${
|
||
(data.expected_review_state_updates || []).map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.dedupe_key || 'unknown')}</strong>
|
||
<small>${escapeHtml(item.expected_current_review_state || 'unknown')} -> ${escapeHtml(item.expected_review_state || 'none')}</small>
|
||
</div>
|
||
<span>${escapeHtml(item.parameter_payload_hash || 'preview')}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 review_state update payload。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">READ-ONLY RESULT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['read_only_query', data.read_only_query_executed],
|
||
['db_connection', data.database_connection_opened],
|
||
['api_updates_review_state', data.api_updates_review_state],
|
||
['review_state_update_executed', data.review_state_update_executed],
|
||
['database_write', data.database_write_executed],
|
||
['scheduler', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="market-intel-deploy-grid mt-3">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">ROWS</p>
|
||
<div class="market-intel-check-list">${
|
||
rows.map(row => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(row.dedupe_key || 'unknown')}</strong>
|
||
<small>${escapeHtml(row.review_state || 'unknown')}</small>
|
||
</div>
|
||
<span>${escapeHtml(row.updated_at || 'pending')}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未執行只讀查詢或查無 row。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">MISMATCH</p>
|
||
<div class="market-intel-check-list">${
|
||
mismatches.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.dedupe_key || 'unknown')}</strong>
|
||
<small>${escapeHtml(item.expected_review_state || 'none')} / ${escapeHtml(item.actual_review_state || 'none')}</small>
|
||
</div>
|
||
<span>CHECK</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">目前沒有 state mismatch。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewDecisionPostwriteSmoke = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">檢查 queue review decision writer post-write smoke 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewDecisionPostwriteSmokeEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewDecisionPostwriteSmoke(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review decision writer post-write smoke 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewDecisionOperatorDrill = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const commands = data.command_sequence || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`ready=${data.operator_drill_ready ? 'yes' : 'no'}`,
|
||
`statements=${(data.statement_summary || {}).statement_count || 0}`,
|
||
`api_write=${data.api_updates_review_state ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只整理 review_state writer 的人工操作 drill;API/UI 不讀 token、不執行 CLI、不更新 review_state。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未產生 drill gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">COMMAND ORDER</p>
|
||
<div class="market-intel-check-list">${
|
||
commands.map(command => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(`${command.step}. ${command.key}`)}</strong>
|
||
<small>${escapeHtml(command.command_shape || '')}</small>
|
||
</div>
|
||
<span>${command.executes_database ? 'DB' : 'NO DB'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未產生命令順序。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
<div class="market-intel-deploy-grid mt-3">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">INPUTS</p>
|
||
<div class="market-intel-check-list">${
|
||
Object.entries(data.input_summaries || {}).map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供輸入摘要。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">WRITE FLAGS</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['api_executes_cli', data.api_executes_cli],
|
||
['api_reads_token', data.api_reads_approval_token],
|
||
['api_updates_review_state', data.api_updates_review_state],
|
||
['review_state_update_executed', data.review_state_update_executed],
|
||
['database_write', data.database_write_executed],
|
||
['scheduler', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewDecisionOperatorDrill = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">產生 queue review decision writer operator drill 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewDecisionOperatorDrillEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewDecisionOperatorDrill(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review decision writer operator drill 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewDecisionRunPackage = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.package_gates || [];
|
||
const manifest = data.payload_manifest || {};
|
||
const artifacts = data.required_artifacts || [];
|
||
const commands = data.command_bundle || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`package=${data.package_ready ? 'ready' : 'blocked'}`,
|
||
`payloads=${manifest.payload_count || 0}`,
|
||
`artifact=${data.package_artifact_created ? 'created' : 'preview'}`,
|
||
`api_write=${data.api_updates_review_state ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只產生 review_state writer run package 預覽;API/UI 不寫檔、不讀 token、不執行 CLI、不更新 review_state。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PACKAGE GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未產生 package gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PAYLOAD MANIFEST</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['payload_count', manifest.payload_count || 0],
|
||
['manifest_hash', manifest.manifest_hash || 'none'],
|
||
['dedupe_keys', (manifest.dedupe_keys || []).join(', ') || 'none'],
|
||
['allowed_next_states', (manifest.allowed_next_states || []).join(', ') || 'none']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="market-intel-deploy-grid mt-3">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">ARTIFACTS</p>
|
||
<div class="market-intel-check-list">${
|
||
artifacts.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.path_shape || '')}</small>
|
||
</div>
|
||
<span>${item.created_by_api ? 'API' : 'MANUAL'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 artifact 清單。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">COMMANDS</p>
|
||
<div class="market-intel-check-list">${
|
||
commands.map(command => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(`${command.step}. ${command.key}`)}</strong>
|
||
<small>${escapeHtml(command.command_shape || '')}</small>
|
||
</div>
|
||
<span>${command.executes_database ? 'DB' : 'NO DB'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未產生命令 bundle。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">WRITE FLAGS</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['api_writes_file', data.api_writes_file],
|
||
['api_executes_cli', data.api_executes_cli],
|
||
['api_reads_token', data.api_reads_approval_token],
|
||
['api_updates_review_state', data.api_updates_review_state],
|
||
['database_write', data.database_write_executed],
|
||
['scheduler', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewDecisionRunPackage = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">產生 queue review decision writer run package 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewDecisionRunPackageEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewDecisionRunPackage(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review decision writer run package 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewDecisionRunReadiness = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const evidence = data.operator_evidence_summary || {};
|
||
const packageSummary = data.run_package_summary || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`cli_ready=${data.ready_for_cli_operator_run ? 'yes' : 'no'}`,
|
||
`api_update=${data.ready_for_api_review_state_update ? 'yes' : 'no'}`,
|
||
`payloads=${packageSummary.payload_count || 0}`,
|
||
`paths=${evidence.artifact_path_count || 0}`,
|
||
`token_api=${evidence.approval_token_submitted_to_api ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只檢查 review_state CLI 更新前的操作員證據;API/UI 不讀 token、不執行 CLI、不產檔、不更新 review_state。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-empty mb-3">manifest=${escapeHtml((packageSummary.manifest_hash || '').slice(0, 16) || 'none')} / artifacts=${escapeHtml(packageSummary.required_artifact_count || 0)} / commands=${escapeHtml(packageSummary.command_count || 0)}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">READINESS GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 readiness gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">EVIDENCE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['review_state_transaction_json_path_recorded', evidence.review_state_transaction_json_path_recorded],
|
||
['backup_artifact_path_recorded', evidence.backup_artifact_path_recorded],
|
||
['preflight_artifact_path_recorded', evidence.preflight_artifact_path_recorded],
|
||
['preflight_only', evidence.operator_confirmed_review_state_preflight_only],
|
||
['shell_only_token', evidence.operator_acknowledged_shell_only_token],
|
||
['approval_token_submitted_to_api', evidence.approval_token_submitted_to_api]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${value ? 'YES' : 'NO'}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">WRITE FLAGS</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['api_executes_cli', data.api_executes_cli],
|
||
['api_reads_token', data.api_reads_approval_token],
|
||
['api_updates_review_state', data.api_updates_review_state],
|
||
['review_state_update_executed', data.review_state_update_executed],
|
||
['database_write', data.database_write_executed],
|
||
['scheduler', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewDecisionRunReadiness = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">檢查 queue review decision writer run readiness 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewDecisionRunReadinessEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewDecisionRunReadiness(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review decision writer run readiness 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewDecisionRunReceipt = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const writer = data.writer_output_summary || {};
|
||
const smoke = data.postwrite_smoke_summary || {};
|
||
const evidence = data.operator_evidence_summary || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`receipt=${data.receipt_passed ? 'pass' : 'blocked'}`,
|
||
`writer=${writer.mode || 'missing'}`,
|
||
`smoke=${smoke.postwrite_smoke_passed ? 'pass' : 'blocked'}`,
|
||
`api_update=${data.ready_for_api_review_state_update ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只審核 review_state CLI 更新後的 writer output 與 post-write smoke receipt;API/UI 不讀 token、不執行 CLI、不連 DB、不補更新 review_state。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-empty mb-3">expected=${escapeHtml((data.expected_dedupe_keys || []).join(', ') || 'none')} / writer=${escapeHtml((writer.observed_dedupe_keys || []).join(', ') || 'none')} / smoke=${escapeHtml((smoke.found_dedupe_keys || []).join(', ') || 'none')}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RECEIPT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 receipt gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">WRITER / SMOKE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['writer_committed', writer.database_commit_executed],
|
||
['writer_review_state_update', writer.review_state_update_executed],
|
||
['writer_dedupe_match', writer.dedupe_keys_match_expected],
|
||
['writer_token_key_detected', writer.approval_token_key_detected],
|
||
['smoke_read_only', smoke.read_only_query_executed],
|
||
['smoke_verified', smoke.review_state_update_verified],
|
||
['smoke_state_mismatch', smoke.state_mismatch_count || 0]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">ARTIFACT EVIDENCE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['writer_output_json_path_recorded', evidence.writer_output_json_path_recorded],
|
||
['postwrite_smoke_json_path_recorded', evidence.postwrite_smoke_json_path_recorded],
|
||
['operator_confirmed_no_token_in_artifacts', evidence.operator_confirmed_no_token_in_artifacts],
|
||
['approval_token_submitted_to_api', evidence.approval_token_submitted_to_api]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${value ? 'YES' : 'NO'}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewDecisionRunReceipt = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">審核 queue review decision writer run receipt 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewDecisionRunReceiptEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewDecisionRunReceipt(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review decision writer run receipt 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewDecisionRunCloseout = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const receipt = data.receipt_summary || {};
|
||
const closeout = data.operator_closeout_summary || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`closeout=${data.closeout_passed ? 'pass' : 'blocked'}`,
|
||
`receipt=${receipt.receipt_passed ? 'pass' : 'blocked'}`,
|
||
`updates=${receipt.expected_review_state_update_count || 0}`,
|
||
`api_update=${data.ready_for_api_review_state_update ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只收尾 review_state writer run closeout;API/UI 不讀 token、不執行 CLI、不連 DB、不更新 review_state、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">CLOSEOUT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 closeout gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RECEIPT SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['target_operation', receipt.target_operation || 'unknown'],
|
||
['receipt_passed', receipt.receipt_passed],
|
||
['safe_boundaries_complete', receipt.safe_boundaries_complete],
|
||
['writer_review_state_update', receipt.writer_review_state_update_executed],
|
||
['smoke_review_state_verified', receipt.postwrite_smoke_review_state_verified],
|
||
['api_updates_review_state', receipt.api_updates_review_state]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR CLOSEOUT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['closeout_artifact_path_recorded', closeout.closeout_artifact_path_recorded],
|
||
['review_state_closeout_next', closeout.operator_confirmed_review_state_closeout_next],
|
||
['inventory_read_only', closeout.operator_confirmed_post_closeout_inventory_read_only],
|
||
['no_scheduler_attach', closeout.operator_confirmed_no_scheduler_attach],
|
||
['no_api_db_write', closeout.operator_confirmed_no_api_db_write],
|
||
['approval_token_submitted_to_api', closeout.approval_token_submitted_to_api]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${value ? 'YES' : 'NO'}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewDecisionRunCloseout = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">收尾 queue review decision writer run closeout 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewDecisionRunCloseoutEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewDecisionRunCloseout(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review decision writer run closeout 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewDecisionPostCloseoutInventory = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const closeout = data.closeout_summary || {};
|
||
const smoke = data.postwrite_smoke_summary || {};
|
||
const table = data.alert_review_queue_table || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`inventory=${data.post_closeout_inventory_ready ? 'pass' : 'blocked'}`,
|
||
`closeout=${closeout.closeout_passed ? 'pass' : 'blocked'}`,
|
||
`query=${data.read_only_query_executed ? 'yes' : 'no'}`,
|
||
`api_update=${data.ready_for_api_review_state_update ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只檢查 review_state closeout 後的只讀 inventory / smoke;API/UI 不讀 token、不執行 CLI、不更新 review_state、不寫 DB、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-empty mb-3">expected=${escapeHtml((data.expected_dedupe_keys || []).join(', ') || 'none')} / found=${escapeHtml((data.found_dedupe_keys || []).join(', ') || 'none')} / missing=${escapeHtml((data.missing_dedupe_keys || []).join(', ') || 'none')}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">INVENTORY GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 inventory gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">READ-ONLY RESULT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['table_exists', table.exists],
|
||
['table_rows', table.row_count == null ? 'n/a' : table.row_count],
|
||
['smoke_passed', smoke.postwrite_smoke_passed],
|
||
['review_state_verified', smoke.review_state_update_verified],
|
||
['state_mismatch_count', smoke.state_mismatch_count || 0],
|
||
['db_write', data.database_write_executed]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">ROW SNAPSHOT</p>
|
||
<div class="market-intel-check-list">${
|
||
(data.row_summaries || []).slice(0, 6).map(row => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(row.dedupe_key || 'unknown')}</strong>
|
||
<small>${escapeHtml(row.review_state || 'unknown')} / score=${escapeHtml(row.total_score == null ? 'n/a' : row.total_score)}</small>
|
||
</div>
|
||
<span>${escapeHtml(row.priority_lane || 'ROW')}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未載入 row snapshot。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewDecisionPostCloseoutInventory = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">檢查 queue review decision post-closeout inventory 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewDecisionPostCloseoutInventoryEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewDecisionPostCloseoutInventory(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review decision post-closeout inventory 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewCompletionArchive = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const manifest = data.archive_manifest || {};
|
||
const inventory = manifest.inventory || {};
|
||
const artifactPaths = manifest.artifact_paths || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`archive=${data.review_completion_archive_ready ? 'pass' : 'blocked'}`,
|
||
`manifest=${data.archive_manifest_ready ? 'ready' : 'draft'}`,
|
||
`rows=${data.row_summary_count || 0}`,
|
||
`api_write=${data.ready_for_api_database_write ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只整理 review completion archive manifest;API/UI 不寫檔、不讀 token、不執行 CLI、不更新 review_state、不寫 DB、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">ARCHIVE GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 archive gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">MANIFEST</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['expected_keys', inventory.expected_dedupe_key_count || 0],
|
||
['found_keys', inventory.found_dedupe_key_count || 0],
|
||
['missing_keys', inventory.missing_dedupe_key_count || 0],
|
||
['row_snapshot', inventory.row_summary_count || 0],
|
||
['state_mismatch', inventory.state_mismatch_count || 0],
|
||
['archive_file_written', data.archive_file_written]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">ARTIFACT PATHS</p>
|
||
<div class="market-intel-check-list">
|
||
${Object.entries(artifactPaths).map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(key)}</strong>
|
||
<small>${escapeHtml(value || 'missing')}</small>
|
||
</div>
|
||
<span>${value ? 'SET' : 'MISS'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 artifact path。</div>'}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewCompletionArchive = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">整理 queue review completion archive 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewCompletionArchiveEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewCompletionArchive(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review completion archive 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewArchiveSummary = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const sections = data.summary_sections || [];
|
||
const artifactPaths = data.artifact_paths || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`summary=${data.archive_summary_ready ? 'ready' : 'blocked'}`,
|
||
`llm=${data.llm_call_executed ? 'called' : 'blocked'}`,
|
||
`telegram=${data.telegram_dispatched ? 'sent' : 'blocked'}`,
|
||
`rows=${data.row_summary_count || 0}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只整理 archive summary input;API/UI 不呼叫 LLM、不派送 Telegram、不寫檔、不讀 token、不執行 CLI、不更新 review_state、不寫 DB、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">SUMMARY GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 summary gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">SUMMARY SECTIONS</p>
|
||
<div class="market-intel-check-list">${
|
||
sections.map(section => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(section.key || 'section')}</strong>
|
||
<small>${escapeHtml((section.facts || []).join(' / '))}</small>
|
||
</div>
|
||
<span>INPUT</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未產生 summary section。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">NO-SIDE-EFFECTS</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['ai_summary_generated', data.ai_summary_generated],
|
||
['llm_call_executed', data.llm_call_executed],
|
||
['telegram_dispatched', data.telegram_dispatched],
|
||
['summary_file_written', data.summary_file_written],
|
||
['database_write_executed', data.database_write_executed],
|
||
['archive_summary_path', artifactPaths.archive_summary_artifact_path || 'missing']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewArchiveSummary = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">整理 queue review archive summary 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewArchiveSummaryEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewArchiveSummary(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review archive summary 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPreflight = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const routePolicy = data.model_route_policy || {};
|
||
const cascade = routePolicy.primary_cascade || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`preflight=${data.ai_summary_preflight_ready ? 'ready' : 'blocked'}`,
|
||
`manual_ollama=${data.ready_for_manual_ollama_summary_run ? 'ready' : 'blocked'}`,
|
||
`llm=${data.llm_call_executed ? 'called' : 'blocked'}`,
|
||
`telegram=${data.telegram_dispatched ? 'sent' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只檢查 AI summary preflight;後續摘要必須 Ollama-first,Gemini 只能在 Ollama cascade 全失敗後備援。API/UI 不呼叫模型、不派送 Telegram、不寫檔、不讀 token、不寫 DB、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PREFLIGHT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 preflight gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">MODEL ROUTE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['primary_policy', routePolicy.primary_policy || 'missing'],
|
||
['fallback_policy', routePolicy.fallback_policy || 'missing'],
|
||
['forbidden_node', routePolicy.app_host_forbidden_as_ollama_node || 'missing']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
${cascade.map(node => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(node.label || node.key || 'ollama')}</strong>
|
||
<small>${escapeHtml(node.host || '')}</small>
|
||
</div>
|
||
<span>OLLAMA</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">NO-SIDE-EFFECTS</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['ready_for_ai_summary_generation', data.ready_for_ai_summary_generation],
|
||
['ready_for_llm_call', data.ready_for_llm_call],
|
||
['ready_for_telegram_dispatch', data.ready_for_telegram_dispatch],
|
||
['ai_summary_generated', data.ai_summary_generated],
|
||
['llm_call_executed', data.llm_call_executed],
|
||
['ollama_call_executed', data.ollama_call_executed],
|
||
['gemini_call_executed', data.gemini_call_executed],
|
||
['telegram_dispatched', data.telegram_dispatched],
|
||
['summary_file_written', data.summary_file_written],
|
||
['database_write_executed', data.database_write_executed],
|
||
['scheduler_attached', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPreflight = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">檢查 queue review AI summary preflight 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewAiSummaryPreflightEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPreflight(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary preflight 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryRunPackage = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const prompt = data.prompt_contract || {};
|
||
const schema = data.summary_output_schema || {};
|
||
const routePolicy = data.model_route_policy || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`package=${data.ai_summary_run_package_ready ? 'ready' : 'blocked'}`,
|
||
`manual_ollama=${data.ready_for_manual_ollama_summary_run ? 'ready' : 'blocked'}`,
|
||
`llm=${data.llm_call_executed ? 'called' : 'blocked'}`,
|
||
`db=${data.database_write_executed ? 'written' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只產生手動 Ollama 摘要任務包 contract;API/UI 不呼叫模型、不寫 run package、不派送 Telegram、不更新 review_state、不寫 DB。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RUN PACKAGE GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 run package gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PROMPT CONTRACT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['contract', prompt.contract_version || 'missing'],
|
||
['language', prompt.language || 'missing'],
|
||
['primary_policy', routePolicy.primary_policy || 'missing'],
|
||
['fallback_policy', routePolicy.fallback_policy || 'missing'],
|
||
['schema', schema.schema_version || 'missing']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">NO-SIDE-EFFECTS</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['ready_for_ai_summary_generation', data.ready_for_ai_summary_generation],
|
||
['api_executes_llm', data.api_executes_llm],
|
||
['llm_call_executed', data.llm_call_executed],
|
||
['ollama_call_executed', data.ollama_call_executed],
|
||
['gemini_call_executed', data.gemini_call_executed],
|
||
['telegram_dispatched', data.telegram_dispatched],
|
||
['run_package_file_written', data.run_package_file_written],
|
||
['summary_file_written', data.summary_file_written],
|
||
['database_write_executed', data.database_write_executed],
|
||
['scheduler_attached', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryRunPackage = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">產生 queue review AI summary run package 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewAiSummaryRunPackageEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryRunPackage(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary run package 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryOutputReceipt = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const validation = data.summary_output_validation || {};
|
||
const routePolicy = data.model_route_policy || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`receipt=${data.ai_summary_output_receipt_ready ? 'ready' : 'blocked'}`,
|
||
`schema=${data.summary_output_schema_valid ? 'pass' : 'blocked'}`,
|
||
`refs=${data.summary_output_evidence_refs_grounded ? 'pass' : 'blocked'}`,
|
||
`llm=${data.llm_call_executed ? 'called' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只驗收人工 Ollama 摘要輸出;API/UI 不呼叫模型、不寫 receipt、不派送 Telegram、不更新 review_state、不寫 DB。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RECEIPT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 output receipt gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OUTPUT VALIDATION</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['missing_fields', (validation.missing_fields || []).join(', ') || 'none'],
|
||
['empty_fields', (validation.empty_fields || []).join(', ') || 'none'],
|
||
['evidence_refs', (validation.evidence_refs || []).join(', ') || 'none'],
|
||
['route_accepted', validation.model_route_accepted],
|
||
['primary_policy', routePolicy.primary_policy || 'missing']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">NO-SIDE-EFFECTS</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['api_executes_llm', data.api_executes_llm],
|
||
['llm_call_executed', data.llm_call_executed],
|
||
['ollama_call_executed', data.ollama_call_executed],
|
||
['gemini_call_executed', data.gemini_call_executed],
|
||
['telegram_dispatched', data.telegram_dispatched],
|
||
['summary_receipt_file_written', data.summary_receipt_file_written],
|
||
['summary_file_written', data.summary_file_written],
|
||
['database_write_executed', data.database_write_executed],
|
||
['scheduler_attached', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryOutputReceipt = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">驗收 queue review AI summary output receipt 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewAiSummaryOutputReceiptEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryOutputReceipt(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary output receipt 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistencePreflight = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const contract = data.summary_persistence_contract || {};
|
||
const record = data.summary_record_preview || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`preflight=${data.summary_persistence_preflight_ready ? 'ready' : 'blocked'}`,
|
||
`transaction=${data.ready_for_summary_transaction_preview ? 'ready' : 'blocked'}`,
|
||
`db=${data.database_write_executed ? 'written' : 'blocked'}`,
|
||
`telegram=${data.telegram_dispatched ? 'sent' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只檢查 AI summary persistence preflight;後續若要落地 metadata_json 必須另開 transaction preview 與 CLI-only writer gate。API/UI 不寫 DB、不派送 Telegram、不呼叫模型。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PERSISTENCE GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 persistence preflight gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">TARGET CONTRACT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['target_table', contract.target_table || 'missing'],
|
||
['target_column', contract.target_column || 'missing'],
|
||
['json_path', (contract.target_json_path || []).join('.') || 'missing'],
|
||
['planned_updates', contract.planned_update_count || 0],
|
||
['api_write_allowed', contract.api_write_allowed]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">SUMMARY RECORD</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['payload_hash', record.payload_hash || 'missing'],
|
||
['headline', record.headline || 'missing'],
|
||
['findings', record.key_finding_count || 0],
|
||
['risks', record.risk_flag_count || 0],
|
||
['actions', record.recommended_action_count || 0],
|
||
['evidence_refs', (record.evidence_refs || []).join(', ') || 'none']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">NO-SIDE-EFFECTS</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['api_executes_llm', data.api_executes_llm],
|
||
['llm_call_executed', data.llm_call_executed],
|
||
['telegram_dispatched', data.telegram_dispatched],
|
||
['summary_persistence_preflight_file_written', data.summary_persistence_preflight_file_written],
|
||
['summary_persistence_record_written', data.summary_persistence_record_written],
|
||
['metadata_patch_written', data.metadata_patch_written],
|
||
['database_write_executed', data.database_write_executed],
|
||
['scheduler_attached', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistencePreflight = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">檢查 queue review AI summary persistence preflight 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewAiSummaryPersistencePreflightEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistencePreflight(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary persistence preflight 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTransaction = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const summary = data.transaction_summary || {};
|
||
const statements = data.statements || [];
|
||
const rollback = data.rollback_plan || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`transaction=${data.summary_persistence_transaction_ready ? 'ready' : 'blocked'}`,
|
||
`statements=${data.statement_count || 0}`,
|
||
`db=${data.database_write_executed ? 'written' : 'blocked'}`,
|
||
`telegram=${data.telegram_dispatched ? 'sent' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只產生 AI summary persistence transaction preview;正式 metadata_json 寫入必須另開 CLI writer gate。API/UI 不開 DB、不寫檔、不派送 Telegram、不呼叫模型。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">TRANSACTION GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 transaction gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['target_table', summary.target_table || 'missing'],
|
||
['target_column', summary.target_column || 'missing'],
|
||
['metadata_key', summary.metadata_key || 'missing'],
|
||
['statement_count', summary.statement_count || 0],
|
||
['api_write_allowed', summary.api_write_allowed]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">STATEMENTS</p>
|
||
<div class="market-intel-check-list">${
|
||
statements.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.statement_type || 'UPDATE')}</strong>
|
||
<small>${escapeHtml((item.where && item.where.dedupe_key) || 'missing')}</small>
|
||
</div>
|
||
<span>${item.execute_in_api ? 'WRITE' : 'PREVIEW'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未產生 statement preview。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">ROLLBACK</p>
|
||
<div class="market-intel-check-list">${
|
||
rollback.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.required ? 'REQUIRED' : 'OPTIONAL'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 rollback plan。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTransaction = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">產生 queue review AI summary persistence transaction preview 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewAiSummaryPersistenceTransactionEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTransaction(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary persistence transaction 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceWriterPreflight = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const contract = data.writer_preflight_contract || {};
|
||
const statements = data.statement_payloads || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`preflight=${data.summary_persistence_writer_preflight_ready ? 'ready' : 'blocked'}`,
|
||
`run_package=${data.ready_for_summary_persistence_run_package ? 'ready' : 'blocked'}`,
|
||
`statements=${data.statement_count || 0}`,
|
||
`db=${data.database_write_executed ? 'written' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只檢查 AI summary persistence writer preflight;正式 metadata_json 寫入仍需未來 CLI-only run package。API/UI 不讀 token、不執行 CLI、不開 DB、不寫檔、不派送 Telegram。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">WRITER PREFLIGHT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 writer preflight gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">WRITER CONTRACT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['target_table', contract.target_table || 'missing'],
|
||
['target_column', contract.target_column || 'missing'],
|
||
['json_path', (contract.target_json_path || []).join('.') || 'missing'],
|
||
['manual_cli_required', contract.manual_cli_required],
|
||
['api_write_allowed', contract.api_write_allowed],
|
||
['backup_required', contract.requires_metadata_json_backup],
|
||
['postwrite_smoke', contract.requires_postwrite_smoke]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">STATEMENT PAYLOADS</p>
|
||
<div class="market-intel-check-list">${
|
||
statements.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.statement_type || 'UPDATE')}</strong>
|
||
<small>${escapeHtml(item.dedupe_key || 'missing')}</small>
|
||
</div>
|
||
<span>${item.execute_in_api ? 'WRITE' : 'CLI'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未載入 statement payload。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">NO-SIDE-EFFECTS</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['api_executes_cli', data.api_executes_cli],
|
||
['api_reads_approval_token', data.api_reads_approval_token],
|
||
['api_writes_database', data.api_writes_database],
|
||
['summary_persistence_writer_preflight_file_written', data.summary_persistence_writer_preflight_file_written],
|
||
['database_connection_opened', data.database_connection_opened],
|
||
['database_write_executed', data.database_write_executed],
|
||
['telegram_dispatched', data.telegram_dispatched],
|
||
['scheduler_attached', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceWriterPreflight = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">檢查 queue review AI summary persistence writer preflight 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewAiSummaryPersistenceWriterPreflightEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceWriterPreflight(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary persistence writer preflight 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceRunPackage = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.package_gates || [];
|
||
const manifest = data.payload_manifest || {};
|
||
const artifacts = data.required_artifacts || [];
|
||
const commands = data.command_bundle || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`package=${data.package_ready ? 'ready' : 'blocked'}`,
|
||
`readiness=${data.ready_for_summary_persistence_run_readiness ? 'ready' : 'blocked'}`,
|
||
`payloads=${manifest.payload_count || 0}`,
|
||
`db=${data.database_write_executed ? 'written' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只產生 AI summary persistence run package preview;正式 metadata_json 寫入仍需後續 operator readiness 與 CLI receipt。API/UI 不讀 token、不執行 CLI、不開 DB、不寫檔、不派送 Telegram。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PACKAGE GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 run package gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PAYLOAD MANIFEST</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['payload_count', manifest.payload_count || 0],
|
||
['manifest_hash', manifest.manifest_hash || 'missing'],
|
||
['summary_hash', manifest.summary_payload_hash || 'missing'],
|
||
['dedupe_keys', (manifest.dedupe_keys || []).join(', ') || 'none']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">REQUIRED ARTIFACTS</p>
|
||
<div class="market-intel-check-list">${
|
||
artifacts.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.path_shape || '')}</small>
|
||
</div>
|
||
<span>${item.created_by_api ? 'API' : 'OPERATOR'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未列出 artifacts。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">COMMAND BUNDLE</p>
|
||
<div class="market-intel-check-list">${
|
||
commands.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.command_shape || '')}</small>
|
||
</div>
|
||
<span>${item.executed ? 'EXECUTED' : item.executes_database ? 'CLI DB' : 'PREP'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 command bundle。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">NO-SIDE-EFFECTS</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['api_executes_cli', data.api_executes_cli],
|
||
['api_reads_approval_token', data.api_reads_approval_token],
|
||
['api_writes_database', data.api_writes_database],
|
||
['package_artifact_created', data.package_artifact_created],
|
||
['summary_persistence_run_package_file_written', data.summary_persistence_run_package_file_written],
|
||
['database_connection_opened', data.database_connection_opened],
|
||
['database_write_executed', data.database_write_executed],
|
||
['telegram_dispatched', data.telegram_dispatched],
|
||
['scheduler_attached', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceRunPackage = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">產生 queue review AI summary persistence run package 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewAiSummaryPersistenceRunPackageEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceRunPackage(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary persistence run package 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceRunReadiness = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const summary = data.run_package_summary || {};
|
||
const operator = data.operator_ai_summary_persistence_run_readiness || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`readiness=${data.run_readiness_ready ? 'ready' : 'blocked'}`,
|
||
`payloads=${summary.payload_count || 0}`,
|
||
`operator_artifacts=${operator.artifact_path_count || 0}`,
|
||
`db=${data.database_write_executed ? 'written' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只檢查 AI summary persistence operator readiness;API/UI 不讀 token、不執行 CLI、不開 DB、不寫 metadata_json、不派送 Telegram。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">READINESS GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 readiness gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PACKAGE SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['package_ready', summary.package_ready],
|
||
['payload_count', summary.payload_count || 0],
|
||
['command_count', summary.command_count || 0],
|
||
['writer_command_present', summary.writer_command_present],
|
||
['manifest_hash', summary.manifest_hash || 'missing']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR EVIDENCE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['run_package_path', operator.ai_summary_persistence_run_package_artifact_path || 'missing'],
|
||
['writer_preflight_path', operator.ai_summary_persistence_writer_preflight_artifact_path || 'missing'],
|
||
['backup_path', operator.metadata_json_backup_artifact_path || 'missing'],
|
||
['postwrite_smoke_path', operator.ai_summary_persistence_postwrite_smoke_artifact_path || 'missing'],
|
||
['token_submitted_to_api', operator.approval_token_submitted_to_api]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">NO-SIDE-EFFECTS</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['api_executes_cli', data.api_executes_cli],
|
||
['api_reads_approval_token', data.api_reads_approval_token],
|
||
['api_writes_database', data.api_writes_database],
|
||
['run_readiness_file_written', data.run_readiness_file_written],
|
||
['database_connection_opened', data.database_connection_opened],
|
||
['database_write_executed', data.database_write_executed],
|
||
['telegram_dispatched', data.telegram_dispatched],
|
||
['scheduler_attached', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceRunReadiness = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">檢查 queue review AI summary persistence run readiness 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceRunReadinessEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceRunReadiness(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary persistence run readiness 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceRunReceipt = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const readiness = data.run_readiness_summary || {};
|
||
const writer = data.writer_output_summary || {};
|
||
const smoke = data.postwrite_smoke_summary || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`receipt=${data.run_receipt_passed ? 'passed' : 'blocked'}`,
|
||
`closeout=${data.ready_for_summary_persistence_closeout ? 'ready' : 'blocked'}`,
|
||
`statements=${data.statement_count || 0}`,
|
||
`db=${data.database_write_executed ? 'written' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只審核 AI summary persistence CLI 寫入後的回執;API/UI 不讀 token、不執行 CLI、不開 DB、不寫 metadata_json、不派送 Telegram。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RECEIPT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 receipt gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RUN READINESS</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['run_ready', readiness.ready_for_cli_operator_run],
|
||
['payload_count', readiness.payload_count || 0],
|
||
['expected_keys', (readiness.expected_dedupe_keys || []).join(', ') || 'none'],
|
||
['summary_hash', readiness.expected_summary_payload_hash || 'missing'],
|
||
['commands_executed', readiness.commands_executed]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">WRITER OUTPUT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', writer.provided],
|
||
['mode', writer.mode || 'missing'],
|
||
['metadata_update', writer.metadata_json_update_executed],
|
||
['dedupe_match', writer.dedupe_keys_match_expected],
|
||
['hash_match', writer.summary_payload_hash_matches_expected],
|
||
['token_key', writer.approval_token_key_detected]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">POST-WRITE SMOKE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', smoke.provided],
|
||
['mode', smoke.mode || 'missing'],
|
||
['read_only', smoke.read_only_query_executed],
|
||
['metadata_verified', smoke.metadata_json_update_verified],
|
||
['dedupe_match', smoke.dedupe_keys_match_expected],
|
||
['db_write', smoke.database_write_executed]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">NO-SIDE-EFFECTS</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['api_executes_cli', data.api_executes_cli],
|
||
['api_reads_approval_token', data.api_reads_approval_token],
|
||
['api_writes_database', data.api_writes_database],
|
||
['run_receipt_file_written', data.run_receipt_file_written],
|
||
['database_connection_opened', data.database_connection_opened],
|
||
['database_write_executed', data.database_write_executed],
|
||
['telegram_dispatched', data.telegram_dispatched],
|
||
['scheduler_attached', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceRunReceipt = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">審核 queue review AI summary persistence run receipt 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceRunReceiptEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceRunReceipt(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary persistence run receipt 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceRunCloseout = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const receipt = data.receipt_summary || {};
|
||
const operator = data.operator_closeout_summary || {};
|
||
const promotion = data.promotion_gate || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`closeout=${data.closeout_passed ? 'passed' : 'blocked'}`,
|
||
`telegram_gate=${data.ready_for_summary_persistence_telegram_dispatch_gate ? 'ready' : 'blocked'}`,
|
||
`statements=${data.statement_count || 0}`,
|
||
`db=${data.database_write_executed ? 'written' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只收尾 AI summary persistence receipt;後續 Telegram dispatch 必須另開 gate。API/UI 不讀 token、不執行 CLI、不開 DB、不寫 metadata_json、不派送 Telegram。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">CLOSEOUT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 closeout gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RECEIPT SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['receipt_passed', receipt.receipt_passed],
|
||
['closeout_ready', receipt.ready_for_summary_persistence_closeout],
|
||
['expected_keys', receipt.expected_dedupe_key_count || 0],
|
||
['writer_metadata', receipt.writer_metadata_json_update_executed],
|
||
['smoke_verified', receipt.postwrite_smoke_metadata_json_verified],
|
||
['safe_boundaries', receipt.safe_boundaries_complete]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR CLOSEOUT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['closeout_artifact', operator.closeout_artifact_path_recorded],
|
||
['closeout_confirmed', operator.operator_confirmed_summary_persistence_closeout],
|
||
['telegram_separate_gate', operator.operator_confirmed_telegram_dispatch_requires_separate_gate],
|
||
['no_api_db_write', operator.operator_confirmed_no_api_db_write],
|
||
['no_telegram_dispatch', operator.operator_confirmed_no_telegram_dispatch],
|
||
['token_submitted', operator.approval_token_submitted_to_api]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PROMOTION</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['allowed', promotion.allowed],
|
||
['next_phase', promotion.next_manual_phase || 'blocked'],
|
||
['requires_real_db_write', promotion.requires_real_db_write],
|
||
['requires_scheduler_attach', promotion.requires_scheduler_attach],
|
||
['telegram_separate_gate', promotion.telegram_dispatch_requires_separate_gate]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">NO-SIDE-EFFECTS</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['api_executes_cli', data.api_executes_cli],
|
||
['api_reads_approval_token', data.api_reads_approval_token],
|
||
['api_writes_database', data.api_writes_database],
|
||
['closeout_file_written', data.closeout_file_written],
|
||
['database_connection_opened', data.database_connection_opened],
|
||
['database_write_executed', data.database_write_executed],
|
||
['telegram_dispatched', data.telegram_dispatched],
|
||
['scheduler_attached', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceRunCloseout = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">收尾 queue review AI summary persistence run closeout 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceRunCloseoutEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceRunCloseout(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary persistence run closeout 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchGate = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const closeout = data.closeout_summary || {};
|
||
const operator = data.operator_telegram_dispatch_gate_summary || {};
|
||
const contract = data.telegram_message_contract || {};
|
||
const promotion = data.promotion_gate || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`dispatch_gate=${data.telegram_dispatch_gate_passed ? 'passed' : 'blocked'}`,
|
||
`run_package=${data.ready_for_summary_persistence_telegram_dispatch_run_package ? 'ready' : 'blocked'}`,
|
||
`statements=${data.statement_count || 0}`,
|
||
`telegram=${data.telegram_dispatched ? 'sent' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只整理 AI summary Telegram dispatch gate;API/UI 不讀 token、不呼叫 LLM、不開 DB、不寫檔、不派送 Telegram、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">DISPATCH GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 dispatch gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">CLOSEOUT SOURCE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['closeout_passed', closeout.closeout_passed],
|
||
['telegram_gate_ready', closeout.ready_for_summary_persistence_telegram_dispatch_gate],
|
||
['expected_keys', closeout.expected_dedupe_key_count || 0],
|
||
['summary_hash', closeout.expected_summary_payload_hash || 'missing'],
|
||
['metadata_smoke', closeout.postwrite_smoke_metadata_json_verified],
|
||
['safe_boundaries', closeout.safe_boundaries_complete]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">MESSAGE CONTRACT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['source', `${contract.source_column || 'metadata'}:${(contract.source_json_path || []).join('.')}`],
|
||
['channel', contract.target_channel || 'manual_gate'],
|
||
['format', contract.message_format || 'unknown'],
|
||
['max_chars', contract.max_message_chars || 0],
|
||
['evidence_refs', contract.requires_evidence_refs],
|
||
['api_dispatches', contract.api_dispatches_telegram]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR GATE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['artifact_path', operator.telegram_dispatch_gate_artifact_path_recorded],
|
||
['dispatch_gate', operator.operator_confirmed_summary_persistence_telegram_dispatch_gate],
|
||
['message_reviewed', operator.operator_confirmed_telegram_message_reviewed],
|
||
['manual_run_package', operator.operator_confirmed_dispatch_requires_manual_run_package],
|
||
['channel_label', operator.telegram_channel_label_recorded],
|
||
['token_submitted', operator.forbidden_token_submitted_to_api]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PROMOTION / NO-SIDE-EFFECTS</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['allowed', promotion.allowed],
|
||
['next_phase', promotion.next_manual_phase || 'blocked'],
|
||
['ready_for_telegram_dispatch', data.ready_for_telegram_dispatch],
|
||
['api_dispatches_telegram', data.api_dispatches_telegram],
|
||
['telegram_dispatched', data.telegram_dispatched],
|
||
['database_write_executed', data.database_write_executed],
|
||
['scheduler_attached', data.scheduler_attached],
|
||
['dispatch_gate_file_written', data.telegram_dispatch_gate_file_written]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchGate = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">檢查 queue review AI summary Telegram dispatch gate 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchGateEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchGate(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch gate 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunPackage = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.package_gates || [];
|
||
const manifest = data.message_package_manifest || {};
|
||
const gate = data.telegram_dispatch_gate_summary || {};
|
||
const operator = data.operator_telegram_dispatch_run_package || {};
|
||
const commands = data.command_bundle || [];
|
||
const artifacts = data.required_artifacts || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`package=${data.telegram_dispatch_run_package_ready ? 'ready' : 'blocked'}`,
|
||
`readiness=${data.ready_for_summary_persistence_telegram_dispatch_run_readiness ? 'ready' : 'blocked'}`,
|
||
`commands=${commands.length}`,
|
||
`telegram=${data.telegram_dispatched ? 'sent' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只產生 AI summary Telegram dispatch run package preview;API/UI 不讀 token、不呼叫 LLM、不開 DB、不寫檔、不派送 Telegram、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PACKAGE GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 package gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">MESSAGE MANIFEST</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['channel', manifest.target_channel || 'manual_gate'],
|
||
['format', manifest.message_format || 'unknown'],
|
||
['dedupe_keys', manifest.expected_dedupe_key_count || 0],
|
||
['summary_hash', manifest.expected_summary_payload_hash || 'missing'],
|
||
['manifest_hash', manifest.manifest_hash || 'missing'],
|
||
['api_dispatches', manifest.api_dispatches_telegram]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">COMMAND PLAN</p>
|
||
<div class="market-intel-check-list">
|
||
${commands.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(`${item.step}. ${item.key}`)}</strong>
|
||
<small>${escapeHtml(item.command_shape || '')}</small>
|
||
</div>
|
||
<span>${item.executed ? 'DONE' : (item.executes_telegram ? 'MANUAL' : 'PLAN')}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 command bundle。</div>'}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">ARTIFACTS</p>
|
||
<div class="market-intel-check-list">
|
||
${artifacts.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.path_shape || '')}</small>
|
||
</div>
|
||
<span>${item.created_by_api ? 'API' : 'OPERATOR'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 artifact checklist。</div>'}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">SOURCE / NO-SIDE-EFFECTS</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['gate_passed', gate.telegram_dispatch_gate_passed],
|
||
['gate_side_effects_clear', gate.side_effects_clear],
|
||
['operator_package', operator.operator_confirmed_ai_summary_telegram_dispatch_run_package],
|
||
['manual_only', operator.operator_confirmed_dispatch_is_manual_only],
|
||
['ready_for_telegram_dispatch', data.ready_for_telegram_dispatch],
|
||
['api_dispatches_telegram', data.api_dispatches_telegram],
|
||
['run_package_file_written', data.telegram_dispatch_run_package_file_written],
|
||
['telegram_dispatched', data.telegram_dispatched],
|
||
['scheduler_attached', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunPackage = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">產生 queue review AI summary Telegram dispatch run package 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunPackageEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunPackage(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch run package 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunReadiness = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const packageSummary = data.telegram_dispatch_run_package_summary || {};
|
||
const operator = data.operator_telegram_dispatch_run_readiness || {};
|
||
const boundary = data.manual_dispatch_boundary || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`readiness=${data.telegram_dispatch_run_readiness_ready ? 'ready' : 'blocked'}`,
|
||
`manual=${data.ready_for_manual_telegram_dispatch ? 'ready' : 'blocked'}`,
|
||
`receipt=${data.ready_for_summary_persistence_telegram_dispatch_run_receipt ? 'ready' : 'blocked'}`,
|
||
`telegram=${data.telegram_dispatched ? 'sent' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只檢查人工 Telegram dispatch readiness;API/UI 不讀 token、不呼叫 LLM、不開 DB、不寫檔、不派送 Telegram、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">READINESS GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 readiness gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RUN PACKAGE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['package_ready', packageSummary.package_ready],
|
||
['channel', packageSummary.target_channel || 'manual_gate'],
|
||
['format', packageSummary.message_format || 'unknown'],
|
||
['dedupe_keys', packageSummary.expected_dedupe_key_count || 0],
|
||
['summary_hash', packageSummary.expected_summary_payload_hash || 'missing'],
|
||
['manual_command', packageSummary.manual_dispatch_command_present],
|
||
['commands_executed', packageSummary.commands_executed]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR EVIDENCE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['artifact_paths', operator.artifact_path_count || 0],
|
||
['run_readiness', operator.operator_confirmed_ai_summary_telegram_dispatch_run_readiness],
|
||
['message_preview', operator.operator_confirmed_message_preview_reviewed],
|
||
['manual_command', operator.operator_confirmed_manual_dispatch_command_reviewed],
|
||
['token_shell_only', operator.operator_confirmed_telegram_token_shell_only],
|
||
['receipt_required', operator.operator_confirmed_dispatch_receipt_required],
|
||
['token_leak', operator.forbidden_token_submitted_to_api]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">MANUAL BOUNDARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['api_may_dispatch_telegram', boundary.api_may_dispatch_telegram],
|
||
['operator_shell_required', boundary.operator_shell_required],
|
||
['telegram_token_shell_only', boundary.telegram_token_shell_only],
|
||
['requires_message_preview_review', boundary.requires_message_preview_review],
|
||
['requires_dispatch_receipt', boundary.requires_dispatch_receipt],
|
||
['api_dispatches_telegram', data.api_dispatches_telegram],
|
||
['telegram_dispatched', data.telegram_dispatched],
|
||
['scheduler_attached', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunReadiness = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">檢查 queue review AI summary Telegram dispatch run readiness 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunReadinessEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunReadiness(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch run readiness 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunReceipt = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const readiness = data.telegram_dispatch_run_readiness_summary || {};
|
||
const receipt = data.telegram_dispatch_receipt_summary || {};
|
||
const operator = data.operator_telegram_dispatch_run_receipt || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`receipt=${data.telegram_dispatch_run_receipt_passed ? 'passed' : 'blocked'}`,
|
||
`closeout=${data.ready_for_summary_persistence_telegram_dispatch_closeout ? 'ready' : 'blocked'}`,
|
||
`message=${receipt.message_id || 'missing'}`,
|
||
`telegram=${data.telegram_dispatched ? 'sent' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只審核人工 Telegram dispatch receipt;API/UI 不讀 token、不呼叫 LLM、不開 DB、不寫檔、不補發 Telegram、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RECEIPT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 receipt gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RUN READINESS</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['readiness_ready', readiness.run_readiness_ready],
|
||
['manual_dispatch', readiness.ready_for_manual_telegram_dispatch],
|
||
['channel', readiness.target_channel || 'manual_gate'],
|
||
['format', readiness.message_format || 'unknown'],
|
||
['dedupe_keys', readiness.expected_dedupe_key_count || 0],
|
||
['summary_hash', readiness.expected_summary_payload_hash || 'missing']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">TELEGRAM RECEIPT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', receipt.provided],
|
||
['mode', receipt.mode || 'missing'],
|
||
['ok', receipt.ok],
|
||
['message_id', receipt.message_id || 'missing'],
|
||
['chat_id', receipt.chat_id || 'missing'],
|
||
['channel', receipt.telegram_channel_label || 'missing'],
|
||
['hash_match', receipt.summary_payload_hash_matches_expected],
|
||
['token_key_detected', receipt.approval_or_telegram_token_key_detected]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR / API BOUNDARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['receipt_artifact', operator.telegram_dispatch_receipt_artifact_path_recorded],
|
||
['message_preview', operator.telegram_message_preview_artifact_path_recorded],
|
||
['operator_receipt', operator.operator_confirmed_telegram_dispatch_receipt],
|
||
['message_id_recorded', operator.operator_confirmed_message_id_recorded],
|
||
['no_token', operator.operator_confirmed_no_token_in_receipt],
|
||
['api_dispatches_telegram', data.api_dispatches_telegram],
|
||
['telegram_dispatched_by_api', data.telegram_dispatched],
|
||
['scheduler_attached', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunReceipt = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">審核 queue review AI summary Telegram dispatch run receipt 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunReceiptEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunReceipt(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch run receipt 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchCloseout = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const receipt = data.telegram_dispatch_run_receipt_summary || {};
|
||
const operator = data.operator_telegram_dispatch_closeout || {};
|
||
const audit = data.dispatch_audit_summary || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`closeout=${data.telegram_dispatch_closeout_passed ? 'passed' : 'blocked'}`,
|
||
`archive=${data.ready_for_summary_persistence_telegram_dispatch_archive ? 'ready' : 'blocked'}`,
|
||
`message=${audit.message_id || 'missing'}`,
|
||
`telegram=${data.telegram_dispatched ? 'sent' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只整理人工 Telegram dispatch closeout;API/UI 不讀 token、不呼叫 LLM、不開 DB、不寫檔、不補發 Telegram、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">CLOSEOUT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 closeout gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RUN RECEIPT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', receipt.provided],
|
||
['mode', receipt.mode || 'missing'],
|
||
['receipt_passed', receipt.receipt_passed],
|
||
['ready_closeout', receipt.ready_for_summary_persistence_telegram_dispatch_closeout],
|
||
['message_id', receipt.message_id || 'missing'],
|
||
['channel', receipt.telegram_channel_label || 'missing'],
|
||
['hash_match', receipt.summary_payload_hash_matches_expected],
|
||
['safe_boundaries', receipt.safe_boundaries_complete]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">DISPATCH AUDIT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['manual_dispatch_proven', audit.manual_dispatch_proven],
|
||
['api_dispatch_blocked', audit.api_dispatch_blocked],
|
||
['duplicate_ruled_out', audit.duplicate_dispatch_ruled_out],
|
||
['receipt_archived', audit.receipt_archived],
|
||
['monitoring', audit.post_closeout_monitoring_confirmed],
|
||
['chat_id', audit.chat_id || 'missing'],
|
||
['summary_hash', audit.summary_payload_hash || 'missing']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR CLOSEOUT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['closeout_artifact', operator.telegram_dispatch_closeout_artifact_path_recorded],
|
||
['receipt_artifact', operator.telegram_dispatch_receipt_artifact_path_recorded],
|
||
['closeout_confirmed', operator.operator_confirmed_telegram_dispatch_closeout],
|
||
['receipt_archived', operator.operator_confirmed_receipt_archived],
|
||
['message_visible', operator.operator_confirmed_message_visible],
|
||
['no_duplicate', operator.operator_confirmed_no_duplicate_dispatch],
|
||
['no_token', operator.operator_confirmed_no_token_in_artifacts],
|
||
['no_api_dispatch', operator.operator_confirmed_no_api_telegram_dispatch],
|
||
['no_db_llm_scheduler', operator.operator_confirmed_no_api_db_write && operator.operator_confirmed_no_llm_call && operator.operator_confirmed_no_scheduler_attach]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchCloseout = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">整理 queue review AI summary Telegram dispatch closeout 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchCloseoutEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchCloseout(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch closeout 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchArchive = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const closeout = data.telegram_dispatch_closeout_summary || {};
|
||
const manifest = data.telegram_dispatch_archive_manifest || {};
|
||
const message = manifest.telegram_message || {};
|
||
const paths = manifest.artifact_paths || {};
|
||
const operator = data.operator_telegram_dispatch_archive || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`archive=${data.telegram_dispatch_archive_ready ? 'ready' : 'blocked'}`,
|
||
`manifest=${data.archive_manifest_ready ? 'ready' : 'blocked'}`,
|
||
`message=${message.message_id || 'missing'}`,
|
||
`telegram=${data.telegram_dispatched ? 'sent' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只整理人工 Telegram dispatch archive manifest;API/UI 不讀 token、不呼叫 LLM、不開 DB、不寫檔、不補發 Telegram、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">ARCHIVE GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 archive gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">CLOSEOUT SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', closeout.provided],
|
||
['mode', closeout.mode || 'missing'],
|
||
['closeout_passed', closeout.closeout_passed],
|
||
['ready_archive', closeout.ready_for_summary_persistence_telegram_dispatch_archive],
|
||
['statement_count', closeout.statement_count || 0],
|
||
['side_effects_clear', closeout.side_effects_clear]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">ARCHIVE MANIFEST</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['message_id', message.message_id || 'missing'],
|
||
['chat_id', message.chat_id || 'missing'],
|
||
['channel', message.telegram_channel_label || 'missing'],
|
||
['hash_match', message.summary_payload_hash_matches_expected],
|
||
['receipt_path', paths.telegram_dispatch_receipt_artifact_path || 'missing'],
|
||
['closeout_path', paths.telegram_dispatch_closeout_artifact_path || 'missing'],
|
||
['archive_path', paths.telegram_dispatch_archive_artifact_path || 'missing']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR ARCHIVE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['archive_confirmed', operator.operator_confirmed_telegram_dispatch_archive],
|
||
['read_only', operator.operator_confirmed_archive_is_read_only],
|
||
['receipt_archived', operator.operator_confirmed_receipt_archived],
|
||
['message_id_archived', operator.operator_confirmed_message_id_archived],
|
||
['audit_archived', operator.operator_confirmed_dispatch_audit_archived],
|
||
['monitoring', operator.operator_confirmed_post_closeout_monitoring],
|
||
['no_duplicate', operator.operator_confirmed_no_duplicate_dispatch],
|
||
['no_token', operator.operator_confirmed_no_token_in_artifacts],
|
||
['no_api_dispatch', operator.operator_confirmed_no_api_telegram_dispatch],
|
||
['no_db_llm_scheduler', operator.operator_confirmed_no_api_db_write && operator.operator_confirmed_no_llm_call && operator.operator_confirmed_no_scheduler_attach]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchArchive = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">整理 queue review AI summary Telegram dispatch archive 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchArchiveEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchArchive(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch archive 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchArchiveSummary = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const archive = data.telegram_dispatch_archive_summary || {};
|
||
const message = archive.message || {};
|
||
const audit = archive.dispatch_audit || {};
|
||
const operator = data.operator_telegram_dispatch_archive_summary || {};
|
||
const sections = data.telegram_dispatch_archive_summary_sections || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`summary=${data.telegram_dispatch_archive_summary_ready ? 'ready' : 'blocked'}`,
|
||
`report=${data.ready_for_summary_persistence_telegram_dispatch_report_input ? 'ready' : 'blocked'}`,
|
||
`message=${message.message_id || 'missing'}`,
|
||
`telegram=${data.telegram_dispatched ? 'sent' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只整理 Telegram dispatch archive summary input;API/UI 不讀 token、不呼叫 LLM、不開 DB、不寫檔、不補發 Telegram、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">SUMMARY GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 archive summary gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">ARCHIVE INPUT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', archive.provided],
|
||
['mode', archive.mode || 'missing'],
|
||
['archive_ready', archive.telegram_dispatch_archive_ready],
|
||
['manifest_ready', archive.archive_manifest_ready],
|
||
['statement_count', archive.statement_count || 0],
|
||
['side_effects_clear', archive.side_effects_clear]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">MESSAGE / AUDIT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['message_id', message.message_id || 'missing'],
|
||
['chat_id', message.chat_id || 'missing'],
|
||
['channel', message.telegram_channel_label || 'missing'],
|
||
['hash_match', message.summary_payload_hash_matches_expected],
|
||
['manual_dispatch', audit.manual_dispatch_proven],
|
||
['api_blocked', audit.api_dispatch_blocked],
|
||
['duplicate_ruled_out', audit.duplicate_dispatch_ruled_out],
|
||
['monitoring', audit.post_closeout_monitoring_confirmed]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">SUMMARY SECTIONS</p>
|
||
<div class="market-intel-check-list">${
|
||
sections.map(section => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(section.key || 'section')}</strong>
|
||
<small>${escapeHtml((section.facts || []).join(' / '))}</small>
|
||
</div>
|
||
<span>INPUT</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 summary sections。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['summary_confirmed', operator.operator_confirmed_telegram_dispatch_archive_summary],
|
||
['read_only', operator.operator_confirmed_summary_is_read_only],
|
||
['artifacts_reviewed', operator.operator_confirmed_message_and_artifacts_reviewed],
|
||
['audit_reviewed', operator.operator_confirmed_dispatch_audit_reviewed],
|
||
['separate_report_gate', operator.operator_confirmed_report_input_requires_separate_gate],
|
||
['no_token', operator.operator_confirmed_no_token_in_artifacts],
|
||
['no_api_dispatch', operator.operator_confirmed_no_api_telegram_dispatch],
|
||
['no_db_llm_scheduler', operator.operator_confirmed_no_api_db_write && operator.operator_confirmed_no_llm_call && operator.operator_confirmed_no_scheduler_attach],
|
||
['summary_path', operator.telegram_dispatch_archive_summary_artifact_path || 'missing']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchArchiveSummary = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">整理 queue review AI summary Telegram dispatch archive summary 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchArchiveSummaryEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchArchiveSummary(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch archive summary 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportInput = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const summary = data.telegram_dispatch_archive_summary_input || {};
|
||
const message = summary.message || {};
|
||
const audit = summary.dispatch_audit || {};
|
||
const operator = data.operator_telegram_dispatch_report_input || {};
|
||
const sections = data.telegram_dispatch_report_input_sections || [];
|
||
const contract = data.telegram_dispatch_report_input_contract || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`report_input=${data.telegram_dispatch_report_input_ready ? 'ready' : 'blocked'}`,
|
||
`run_package=${data.ready_for_market_intel_report_run_package ? 'ready' : 'blocked'}`,
|
||
`message=${message.message_id || 'missing'}`,
|
||
`telegram=${data.telegram_dispatched ? 'sent' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只整理 Telegram dispatch report input;API/UI 不讀 token、不呼叫 LLM、不開 DB、不寫檔、不產報表、不補發 Telegram、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">REPORT INPUT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 report input gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">ARCHIVE SUMMARY INPUT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', summary.provided],
|
||
['mode', summary.mode || 'missing'],
|
||
['summary_ready', summary.telegram_dispatch_archive_summary_ready],
|
||
['report_ready', summary.ready_for_summary_persistence_telegram_dispatch_report_input],
|
||
['statement_count', summary.statement_count || 0],
|
||
['side_effects_clear', summary.side_effects_clear]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">REPORT CONTRACT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['contract', contract.contract_version || 'missing'],
|
||
['family', contract.target_report_family || 'missing'],
|
||
['language', contract.language || 'missing'],
|
||
['required_sections', (contract.required_sections || []).join(', ') || 'missing']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">MESSAGE / AUDIT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['message_id', message.message_id || 'missing'],
|
||
['chat_id', message.chat_id || 'missing'],
|
||
['channel', message.telegram_channel_label || 'missing'],
|
||
['hash_match', message.summary_payload_hash_matches_expected],
|
||
['manual_dispatch', audit.manual_dispatch_proven],
|
||
['api_blocked', audit.api_dispatch_blocked],
|
||
['duplicate_ruled_out', audit.duplicate_dispatch_ruled_out],
|
||
['monitoring', audit.post_closeout_monitoring_confirmed]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">REPORT SECTIONS</p>
|
||
<div class="market-intel-check-list">${
|
||
sections.map(section => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(section.key || 'section')}</strong>
|
||
<small>${escapeHtml((section.facts || []).join(' / '))}</small>
|
||
</div>
|
||
<span>REPORT</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 report sections。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR REPORT INPUT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['report_confirmed', operator.operator_confirmed_telegram_dispatch_report_input],
|
||
['read_only', operator.operator_confirmed_report_input_is_read_only],
|
||
['archive_summary_reviewed', operator.operator_confirmed_archive_summary_reviewed],
|
||
['sections_reviewed', operator.operator_confirmed_report_sections_reviewed],
|
||
['separate_report_gate', operator.operator_confirmed_report_generation_requires_separate_gate],
|
||
['no_token', operator.operator_confirmed_no_token_in_report_input],
|
||
['no_api_dispatch', operator.operator_confirmed_no_api_telegram_dispatch],
|
||
['no_db_llm_scheduler', operator.operator_confirmed_no_api_db_write && operator.operator_confirmed_no_llm_call && operator.operator_confirmed_no_scheduler_attach],
|
||
['report_input_path', operator.telegram_dispatch_report_input_artifact_path || 'missing']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportInput = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">整理 queue review AI summary Telegram dispatch report input 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportInputEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportInput(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch report input 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunPackage = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const inputSummary = data.telegram_dispatch_report_input_summary || {};
|
||
const evidence = inputSummary.evidence_refs || {};
|
||
const runPackage = data.telegram_dispatch_report_run_package || {};
|
||
const sections = data.telegram_dispatch_report_run_package_sections || [];
|
||
const operator = data.operator_telegram_dispatch_report_run_package || {};
|
||
const boundaries = runPackage.execution_boundaries || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`package=${data.telegram_dispatch_report_run_package_ready ? 'ready' : 'blocked'}`,
|
||
`readiness=${data.ready_for_market_intel_report_run_readiness ? 'ready' : 'blocked'}`,
|
||
`report=${data.ready_for_report_generation ? 'generated' : 'blocked'}`,
|
||
`telegram=${data.telegram_dispatched ? 'sent' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只整理 Telegram dispatch report run package;API/UI 不讀 token、不呼叫 LLM、不開 DB、不寫檔、不產報表、不補發 Telegram、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">REPORT RUN PACKAGE GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 run package gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">REPORT INPUT SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', inputSummary.provided],
|
||
['mode', inputSummary.mode || 'missing'],
|
||
['input_ready', inputSummary.telegram_dispatch_report_input_ready],
|
||
['run_package_ready', inputSummary.ready_for_market_intel_report_run_package],
|
||
['contract', inputSummary.report_contract_version || 'missing'],
|
||
['family', inputSummary.target_report_family || 'missing'],
|
||
['statement_count', inputSummary.statement_count || 0],
|
||
['side_effects_clear', inputSummary.side_effects_clear]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RUN PACKAGE CONTRACT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['contract', runPackage.contract_version || 'missing'],
|
||
['source_contract', runPackage.source_contract_version || 'missing'],
|
||
['family', runPackage.target_report_family || 'missing'],
|
||
['language', runPackage.language || 'missing'],
|
||
['required_sections', (runPackage.required_package_sections || []).join(', ') || 'missing']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PACKAGE SECTIONS</p>
|
||
<div class="market-intel-check-list">${
|
||
sections.map(section => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(section.key || 'section')}</strong>
|
||
<small>${escapeHtml((section.facts || []).join(' / '))}</small>
|
||
</div>
|
||
<span>PACKAGE</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 run package sections。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">EVIDENCE / BOUNDARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['message_id', evidence.message_id || 'missing'],
|
||
['chat_id', evidence.chat_id || 'missing'],
|
||
['channel', evidence.telegram_channel_label || 'missing'],
|
||
['summary_hash', evidence.summary_payload_hash || 'missing'],
|
||
['api_generates_report', boundaries.api_generates_report],
|
||
['api_writes_report_file', boundaries.api_writes_report_file],
|
||
['api_calls_llm', boundaries.api_calls_llm],
|
||
['api_dispatches_telegram', boundaries.api_dispatches_telegram],
|
||
['api_writes_database', boundaries.api_writes_database],
|
||
['api_attaches_scheduler', boundaries.api_attaches_scheduler]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR PACKAGE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['package_confirmed', operator.operator_confirmed_telegram_dispatch_report_run_package],
|
||
['read_only', operator.operator_confirmed_report_run_package_is_read_only],
|
||
['input_reviewed', operator.operator_confirmed_report_input_reviewed],
|
||
['package_reviewed', operator.operator_confirmed_report_package_reviewed],
|
||
['readiness_gate', operator.operator_confirmed_report_generation_requires_readiness_gate],
|
||
['no_token', operator.operator_confirmed_no_token_in_report_run_package],
|
||
['no_api_dispatch', operator.operator_confirmed_no_api_telegram_dispatch],
|
||
['no_db_llm_scheduler', operator.operator_confirmed_no_api_db_write && operator.operator_confirmed_no_llm_call && operator.operator_confirmed_no_scheduler_attach],
|
||
['package_path', operator.telegram_dispatch_report_run_package_artifact_path || 'missing']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunPackage = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">整理 queue review AI summary Telegram dispatch report run package 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunPackageEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunPackage(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch report run package 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunReadiness = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const packageSummary = data.telegram_dispatch_report_run_package_summary || {};
|
||
const evidence = packageSummary.evidence_refs || {};
|
||
const manifest = data.report_generation_readiness_manifest || {};
|
||
const command = manifest.manual_generation_command || {};
|
||
const boundaries = manifest.execution_boundaries || {};
|
||
const operator = data.operator_telegram_dispatch_report_run_readiness || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`readiness=${data.telegram_dispatch_report_run_readiness_ready ? 'ready' : 'blocked'}`,
|
||
`manual_report=${data.ready_for_manual_report_generation ? 'ready' : 'blocked'}`,
|
||
`report=${data.ready_for_report_generation ? 'generated' : 'blocked'}`,
|
||
`telegram=${data.telegram_dispatched ? 'sent' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只檢查 Telegram dispatch report run readiness;API/UI 不讀 token、不呼叫 LLM、不開 DB、不寫檔、不產報表、不補發 Telegram、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">REPORT RUN READINESS GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 readiness gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RUN PACKAGE SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', packageSummary.provided],
|
||
['mode', packageSummary.mode || 'missing'],
|
||
['package_ready', packageSummary.telegram_dispatch_report_run_package_ready],
|
||
['readiness_ready', packageSummary.ready_for_market_intel_report_run_readiness],
|
||
['contract', packageSummary.package_contract_version || 'missing'],
|
||
['family', packageSummary.target_report_family || 'missing'],
|
||
['statement_count', packageSummary.statement_count || 0],
|
||
['side_effects_clear', packageSummary.side_effects_clear]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">READINESS MANIFEST</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['manifest', manifest.manifest_version || 'missing'],
|
||
['source_contract', manifest.source_contract_version || 'missing'],
|
||
['family', manifest.target_report_family || 'missing'],
|
||
['language', manifest.language || 'missing'],
|
||
['intended_artifacts', (manifest.intended_report_artifacts || []).join(', ') || 'missing']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">MANUAL REPORT COMMAND</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['key', command.key || 'missing'],
|
||
['executes_report_generation', command.executes_report_generation],
|
||
['executes_database', command.executes_database],
|
||
['executes_telegram', command.executes_telegram],
|
||
['executed_by_api', command.executed_by_api],
|
||
['command_shape', command.command_shape || 'missing']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">EVIDENCE / BOUNDARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['message_id', evidence.message_id || 'missing'],
|
||
['chat_id', evidence.chat_id || 'missing'],
|
||
['channel', evidence.telegram_channel_label || 'missing'],
|
||
['summary_hash', evidence.summary_payload_hash || 'missing'],
|
||
['api_generates_report', boundaries.api_generates_report],
|
||
['api_writes_report_file', boundaries.api_writes_report_file],
|
||
['api_calls_llm', boundaries.api_calls_llm],
|
||
['api_dispatches_telegram', boundaries.api_dispatches_telegram],
|
||
['api_writes_database', boundaries.api_writes_database],
|
||
['api_attaches_scheduler', boundaries.api_attaches_scheduler]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR READINESS</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['readiness_confirmed', operator.operator_confirmed_telegram_dispatch_report_run_readiness],
|
||
['package_reviewed', operator.operator_confirmed_report_run_package_reviewed],
|
||
['contract_reviewed', operator.operator_confirmed_report_contract_reviewed],
|
||
['manual_only', operator.operator_confirmed_report_generation_is_manual_only],
|
||
['receipt_required', operator.operator_confirmed_report_output_requires_receipt],
|
||
['no_token', operator.operator_confirmed_no_token_in_report_readiness],
|
||
['no_api_generation', operator.operator_confirmed_no_api_report_generation],
|
||
['no_db_llm_scheduler', operator.operator_confirmed_no_api_db_write && operator.operator_confirmed_no_llm_call && operator.operator_confirmed_no_scheduler_attach],
|
||
['readiness_path', operator.telegram_dispatch_report_run_readiness_artifact_path || 'missing'],
|
||
['output_path', operator.report_output_artifact_path || 'missing']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunReadiness = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">檢查 queue review AI summary Telegram dispatch report run readiness 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunReadinessEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunReadiness(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch report run readiness 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunReceipt = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const readiness = data.telegram_dispatch_report_run_readiness_summary || {};
|
||
const receipt = data.telegram_dispatch_report_run_receipt_summary || {};
|
||
const operator = data.operator_telegram_dispatch_report_run_receipt || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`receipt=${data.telegram_dispatch_report_run_receipt_passed ? 'pass' : 'blocked'}`,
|
||
`closeout=${data.ready_for_market_intel_report_closeout ? 'ready' : 'blocked'}`,
|
||
`report=${data.ready_for_report_generation ? 'generated' : 'blocked'}`,
|
||
`telegram=${data.telegram_dispatched ? 'sent' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只審核 Telegram dispatch report run receipt;API/UI 不讀 token、不呼叫 LLM、不開 DB、不寫檔、不產報表、不補發 Telegram、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">REPORT RUN RECEIPT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 receipt gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">READINESS SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', readiness.provided],
|
||
['mode', readiness.mode || 'missing'],
|
||
['readiness_ready', readiness.telegram_dispatch_report_run_readiness_ready],
|
||
['receipt_ready', readiness.ready_for_market_intel_report_run_receipt],
|
||
['manual_report', readiness.ready_for_manual_report_generation],
|
||
['manifest', readiness.manifest_version || 'missing'],
|
||
['family', readiness.target_report_family || 'missing'],
|
||
['side_effects_clear', readiness.side_effects_clear]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">REPORT RECEIPT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', receipt.provided],
|
||
['mode', receipt.mode || 'missing'],
|
||
['generated', receipt.report_generated],
|
||
['family', receipt.target_report_family || 'missing'],
|
||
['language', receipt.language || 'missing'],
|
||
['summary_hash_match', receipt.summary_payload_hash_matches_expected],
|
||
['sections_present', receipt.required_report_sections_present],
|
||
['artifact_path', receipt.report_output_artifact_path || 'missing']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RUNTIME BOUNDARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['generated_by_api', receipt.generated_by_api],
|
||
['api_writes_file', receipt.api_writes_file],
|
||
['api_calls_llm', receipt.api_calls_llm],
|
||
['api_dispatches_telegram', receipt.api_dispatches_telegram],
|
||
['api_writes_database', receipt.api_writes_database],
|
||
['scheduler_attached', receipt.scheduler_attached],
|
||
['token_key_detected', receipt.approval_or_telegram_token_key_detected]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR RECEIPT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['package_path', operator.telegram_dispatch_report_run_package_artifact_path_recorded],
|
||
['readiness_path', operator.telegram_dispatch_report_run_readiness_artifact_path_recorded],
|
||
['output_path', operator.report_output_artifact_path_recorded],
|
||
['receipt_path', operator.report_run_receipt_artifact_path_recorded],
|
||
['receipt_confirmed', operator.operator_confirmed_report_run_receipt],
|
||
['hash_confirmed', operator.operator_confirmed_report_output_hash_matched],
|
||
['sections_reviewed', operator.operator_confirmed_report_sections_reviewed],
|
||
['no_api_generation', operator.operator_confirmed_no_api_report_generation],
|
||
['no_db_llm_scheduler', operator.operator_confirmed_no_api_db_write && operator.operator_confirmed_no_llm_call && operator.operator_confirmed_no_scheduler_attach]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunReceipt = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">審核 queue review AI summary Telegram dispatch report run receipt 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunReceiptEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunReceipt(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch report run receipt 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCloseout = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const receipt = data.telegram_dispatch_report_run_receipt_summary || {};
|
||
const operator = data.operator_telegram_dispatch_report_closeout || {};
|
||
const promotion = data.promotion_gate || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`closeout=${data.telegram_dispatch_report_closeout_passed ? 'pass' : 'blocked'}`,
|
||
`archive=${data.ready_for_market_intel_report_archive ? 'ready' : 'blocked'}`,
|
||
`report=${data.ready_for_report_generation ? 'generated' : 'blocked'}`,
|
||
`telegram=${data.telegram_dispatched ? 'sent' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只收尾 Telegram dispatch report closeout;API/UI 不讀 token、不呼叫 LLM、不開 DB、不寫檔、不產報表、不補發 Telegram、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">REPORT CLOSEOUT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 closeout gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RECEIPT SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', receipt.provided],
|
||
['mode', receipt.mode || 'missing'],
|
||
['receipt_passed', receipt.receipt_passed],
|
||
['closeout_ready', receipt.ready_for_market_intel_report_closeout],
|
||
['family', receipt.target_report_family || 'missing'],
|
||
['language', receipt.language || 'missing'],
|
||
['report_path', receipt.report_output_artifact_path || 'missing'],
|
||
['summary_hash_match', receipt.summary_payload_hash_matches_expected],
|
||
['sections_present', receipt.required_report_sections_present]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR CLOSEOUT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['closeout_path', operator.report_closeout_artifact_path_recorded],
|
||
['receipt_path', operator.report_run_receipt_artifact_path_recorded],
|
||
['output_path', operator.report_output_artifact_path_recorded],
|
||
['closeout_confirmed', operator.operator_confirmed_report_closeout],
|
||
['receipt_archived', operator.operator_confirmed_report_receipt_archived],
|
||
['hash_confirmed', operator.operator_confirmed_report_output_hash_matched],
|
||
['sections_reviewed', operator.operator_confirmed_report_sections_reviewed],
|
||
['archive_separate_gate', operator.operator_confirmed_report_archive_requires_separate_gate],
|
||
['no_token_or_runtime_side_effect', operator.operator_confirmed_no_token_in_report_closeout && operator.operator_confirmed_no_api_report_generation && operator.operator_confirmed_no_api_telegram_dispatch && operator.operator_confirmed_no_api_db_write && operator.operator_confirmed_no_llm_call && operator.operator_confirmed_no_scheduler_attach]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RUNTIME BOUNDARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['api_writes_file', data.api_writes_file],
|
||
['api_calls_llm', data.api_executes_llm],
|
||
['api_dispatches_telegram', data.api_dispatches_telegram],
|
||
['api_writes_database', data.api_writes_database],
|
||
['database_write', data.database_write_executed],
|
||
['scheduler_attached', data.scheduler_attached],
|
||
['report_file_written', data.report_file_written],
|
||
['closeout_file_written', data.report_closeout_file_written]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PROMOTION</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['allowed', promotion.allowed],
|
||
['next_phase', promotion.next_manual_phase || 'missing'],
|
||
['requires_operator_approval', promotion.requires_operator_approval],
|
||
['archive_separate_gate', promotion.report_archive_requires_separate_gate],
|
||
['api_must_not_generate_report', promotion.api_must_not_generate_report],
|
||
['api_must_not_dispatch_telegram', promotion.api_must_not_dispatch_telegram]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCloseout = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">收尾 queue review AI summary Telegram dispatch report closeout 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCloseoutEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCloseout(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch report closeout 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportArchive = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const closeout = data.telegram_dispatch_report_closeout_summary || {};
|
||
const operator = data.operator_telegram_dispatch_report_archive || {};
|
||
const promotion = data.promotion_gate || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`archive=${data.telegram_dispatch_report_archive_passed ? 'pass' : 'blocked'}`,
|
||
`summary=${data.ready_for_market_intel_report_archive_summary ? 'ready' : 'blocked'}`,
|
||
`file=${data.report_archive_file_written ? 'written' : 'blocked'}`,
|
||
`db=${data.database_write_executed ? 'written' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只審核 Telegram dispatch report archive;API/UI 不讀 token、不呼叫 LLM、不開 DB、不寫檔、不產報表、不補發 Telegram、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">REPORT ARCHIVE GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 archive gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">CLOSEOUT SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', closeout.provided],
|
||
['mode', closeout.mode || 'missing'],
|
||
['closeout_passed', closeout.closeout_passed],
|
||
['archive_ready', closeout.ready_for_market_intel_report_archive],
|
||
['next_phase', closeout.promotion_next_manual_phase || 'missing'],
|
||
['family', closeout.target_report_family || 'missing'],
|
||
['report_path', closeout.report_output_artifact_path || 'missing'],
|
||
['summary_hash_match', closeout.summary_payload_hash_matches_expected],
|
||
['sections_present', closeout.required_report_sections_present]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR ARCHIVE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['archive_path', operator.report_archive_artifact_path_recorded],
|
||
['closeout_path', operator.report_closeout_artifact_path_recorded],
|
||
['receipt_path', operator.report_run_receipt_artifact_path_recorded],
|
||
['output_path', operator.report_output_artifact_path_recorded],
|
||
['archive_confirmed', operator.operator_confirmed_report_archive],
|
||
['closeout_archived', operator.operator_confirmed_report_closeout_archived],
|
||
['receipt_archived', operator.operator_confirmed_report_receipt_archived],
|
||
['output_archived', operator.operator_confirmed_report_output_archived],
|
||
['hash_confirmed', operator.operator_confirmed_report_output_hash_matched],
|
||
['manifest_retention', operator.operator_confirmed_archive_manifest_reviewed && operator.operator_confirmed_archive_retention_policy_recorded]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RUNTIME BOUNDARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['api_writes_file', data.api_writes_file],
|
||
['archive_file_written', data.report_archive_file_written],
|
||
['api_calls_llm', data.api_executes_llm],
|
||
['api_dispatches_telegram', data.api_dispatches_telegram],
|
||
['api_writes_database', data.api_writes_database],
|
||
['database_write', data.database_write_executed],
|
||
['scheduler_attached', data.scheduler_attached],
|
||
['report_file_written', data.report_file_written]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PROMOTION</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['allowed', promotion.allowed],
|
||
['next_phase', promotion.next_manual_phase || 'missing'],
|
||
['requires_operator_approval', promotion.requires_operator_approval],
|
||
['summary_separate_gate', promotion.report_archive_summary_requires_separate_gate],
|
||
['api_must_not_write_archive_file', promotion.api_must_not_write_archive_file],
|
||
['api_must_not_dispatch_telegram', promotion.api_must_not_dispatch_telegram]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportArchive = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">審核 queue review AI summary Telegram dispatch report archive 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportArchiveEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportArchive(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch report archive 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportArchiveSummary = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const summary = data.telegram_dispatch_report_archive_summary || {};
|
||
const operator = data.operator_telegram_dispatch_report_archive_summary || {};
|
||
const sections = data.telegram_dispatch_report_archive_summary_sections || [];
|
||
const promotion = data.promotion_gate || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`summary=${data.telegram_dispatch_report_archive_summary_passed ? 'pass' : 'blocked'}`,
|
||
`catalog=${data.ready_for_market_intel_report_catalog_handoff ? 'ready' : 'blocked'}`,
|
||
`file=${data.report_archive_summary_file_written ? 'written' : 'blocked'}`,
|
||
`db=${data.database_write_executed ? 'written' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只整理 Telegram dispatch report archive summary;API/UI 不讀 token、不呼叫 LLM、不開 DB、不寫檔、不產報表、不補發 Telegram、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">REPORT ARCHIVE SUMMARY GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 archive summary gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">SUMMARY SECTIONS</p>
|
||
<div class="market-intel-check-list">${
|
||
sections.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml((item.facts || []).join(' / '))}</small>
|
||
</div>
|
||
<span>READY</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 summary sections。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">ARCHIVE SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', summary.provided],
|
||
['mode', summary.mode || 'missing'],
|
||
['archive_passed', summary.archive_passed],
|
||
['report_family', summary.target_report_family || 'missing'],
|
||
['report_path', summary.report_output_artifact_path || 'missing'],
|
||
['hash_match', summary.summary_payload_hash_matches_expected],
|
||
['sections_present', summary.required_report_sections_present],
|
||
['retention_recorded', summary.operator_confirmed_archive_retention_policy_recorded]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['summary_path', operator.report_archive_summary_artifact_path || 'missing'],
|
||
['archive_path', operator.report_archive_artifact_path_recorded],
|
||
['closeout_path', operator.report_closeout_artifact_path_recorded],
|
||
['receipt_path', operator.report_run_receipt_artifact_path_recorded],
|
||
['output_path', operator.report_output_artifact_path_recorded],
|
||
['summary_confirmed', operator.operator_confirmed_report_archive_summary],
|
||
['catalog_handoff_gate', operator.operator_confirmed_report_catalog_handoff_requires_separate_gate],
|
||
['runtime_clear', operator.operator_confirmed_no_token_in_report_archive_summary && operator.operator_confirmed_no_api_report_generation && operator.operator_confirmed_no_api_file_write && operator.operator_confirmed_no_api_telegram_dispatch && operator.operator_confirmed_no_api_db_write && operator.operator_confirmed_no_llm_call && operator.operator_confirmed_no_scheduler_attach]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PROMOTION</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['allowed', promotion.allowed],
|
||
['next_phase', promotion.next_manual_phase || 'missing'],
|
||
['catalog_handoff_separate_gate', promotion.report_catalog_handoff_requires_separate_gate],
|
||
['api_must_not_generate_report', promotion.api_must_not_generate_report],
|
||
['api_must_not_write_summary_file', promotion.api_must_not_write_summary_file],
|
||
['api_must_not_dispatch_telegram', promotion.api_must_not_dispatch_telegram]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportArchiveSummary = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">整理 queue review AI summary Telegram dispatch report archive summary 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportArchiveSummaryEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportArchiveSummary(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch report archive summary 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogHandoff = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const summary = data.telegram_dispatch_report_archive_summary || {};
|
||
const operator = data.operator_telegram_dispatch_report_catalog_handoff || {};
|
||
const sections = data.telegram_dispatch_report_catalog_handoff_sections || [];
|
||
const promotion = data.promotion_gate || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`handoff=${data.telegram_dispatch_report_catalog_handoff_passed ? 'pass' : 'blocked'}`,
|
||
`index=${data.ready_for_market_intel_report_catalog_index ? 'ready' : 'blocked'}`,
|
||
`catalog=${data.catalog_record_written ? 'written' : 'blocked'}`,
|
||
`db=${data.database_write_executed ? 'written' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只整理 Telegram dispatch report catalog handoff;API/UI 不讀 token、不呼叫 LLM、不開 DB、不寫 catalog record、不產報表、不補發 Telegram、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">CATALOG HANDOFF GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 catalog handoff gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">HANDOFF SECTIONS</p>
|
||
<div class="market-intel-check-list">${
|
||
sections.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml((item.facts || []).join(' / '))}</small>
|
||
</div>
|
||
<span>READY</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 handoff sections。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">ARCHIVE SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', summary.provided],
|
||
['mode', summary.mode || 'missing'],
|
||
['summary_passed', summary.archive_summary_passed],
|
||
['report_family', summary.target_report_family || 'missing'],
|
||
['report_hash', summary.report_output_hash || 'missing'],
|
||
['section_count', summary.section_count || 0],
|
||
['catalog_ready', summary.ready_for_market_intel_report_catalog_handoff]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR HANDOFF</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['handoff_path', operator.report_catalog_handoff_artifact_path || 'missing'],
|
||
['summary_path', operator.report_archive_summary_artifact_path_recorded],
|
||
['archive_path', operator.report_archive_artifact_path_recorded],
|
||
['output_path', operator.report_output_artifact_path_recorded],
|
||
['catalog_key', operator.catalog_record_key_recorded],
|
||
['catalog_family', operator.catalog_family_recorded],
|
||
['index_gate', operator.operator_confirmed_report_catalog_index_requires_separate_gate],
|
||
['runtime_clear', operator.operator_confirmed_no_token_in_report_catalog_handoff && operator.operator_confirmed_no_api_report_generation && operator.operator_confirmed_no_api_file_write && operator.operator_confirmed_no_api_telegram_dispatch && operator.operator_confirmed_no_api_db_write && operator.operator_confirmed_no_llm_call && operator.operator_confirmed_no_scheduler_attach]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PROMOTION</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['allowed', promotion.allowed],
|
||
['next_phase', promotion.next_manual_phase || 'missing'],
|
||
['catalog_index_separate_gate', promotion.report_catalog_index_requires_separate_gate],
|
||
['write_preflight_separate_gate', promotion.report_catalog_write_preflight_requires_separate_gate],
|
||
['api_must_not_write_catalog_record', promotion.api_must_not_write_catalog_record],
|
||
['api_must_not_dispatch_telegram', promotion.api_must_not_dispatch_telegram]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogHandoff = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">整理 queue review AI summary Telegram dispatch report catalog handoff 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogHandoffEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogHandoff(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch report catalog handoff 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogIndex = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const handoff = data.telegram_dispatch_report_catalog_handoff || {};
|
||
const operator = data.operator_telegram_dispatch_report_catalog_index || {};
|
||
const sections = data.telegram_dispatch_report_catalog_index_sections || [];
|
||
const promotion = data.promotion_gate || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`index=${data.telegram_dispatch_report_catalog_index_passed ? 'pass' : 'blocked'}`,
|
||
`write_preflight=${data.ready_for_market_intel_report_catalog_write_preflight ? 'ready' : 'blocked'}`,
|
||
`catalog=${data.catalog_record_written ? 'written' : 'blocked'}`,
|
||
`db=${data.database_write_executed ? 'written' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只整理 Telegram dispatch report catalog index;API/UI 不讀 token、不呼叫 LLM、不開 DB、不寫 catalog record、不產報表、不補發 Telegram、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">CATALOG INDEX GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 catalog index gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">INDEX SECTIONS</p>
|
||
<div class="market-intel-check-list">${
|
||
sections.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml((item.facts || []).join(' / '))}</small>
|
||
</div>
|
||
<span>READY</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 index sections。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">CATALOG HANDOFF</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', handoff.provided],
|
||
['mode', handoff.mode || 'missing'],
|
||
['handoff_passed', handoff.catalog_handoff_passed],
|
||
['report_family', handoff.target_report_family || 'missing'],
|
||
['report_hash', handoff.report_output_hash || 'missing'],
|
||
['section_count', handoff.section_count || 0],
|
||
['index_ready', handoff.ready_for_market_intel_report_catalog_index]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR INDEX</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['index_path', operator.report_catalog_index_artifact_path || 'missing'],
|
||
['handoff_path', operator.report_catalog_handoff_artifact_path_recorded],
|
||
['summary_path', operator.report_archive_summary_artifact_path_recorded],
|
||
['output_path', operator.report_output_artifact_path_recorded],
|
||
['catalog_key', operator.catalog_record_key_recorded],
|
||
['catalog_family', operator.catalog_family_recorded],
|
||
['index_key', operator.catalog_index_key_recorded],
|
||
['write_preflight_gate', operator.operator_confirmed_report_catalog_write_preflight_requires_separate_gate],
|
||
['runtime_clear', operator.operator_confirmed_no_token_in_report_catalog_index && operator.operator_confirmed_no_api_report_generation && operator.operator_confirmed_no_api_file_write && operator.operator_confirmed_no_api_catalog_record_write && operator.operator_confirmed_no_api_telegram_dispatch && operator.operator_confirmed_no_api_db_write && operator.operator_confirmed_no_llm_call && operator.operator_confirmed_no_scheduler_attach]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PROMOTION</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['allowed', promotion.allowed],
|
||
['next_phase', promotion.next_manual_phase || 'missing'],
|
||
['write_preflight_separate_gate', promotion.report_catalog_write_preflight_requires_separate_gate],
|
||
['record_write_separate_gate', promotion.report_catalog_record_write_requires_separate_gate],
|
||
['api_must_not_write_index_file', promotion.api_must_not_write_catalog_index_file],
|
||
['api_must_not_write_catalog_record', promotion.api_must_not_write_catalog_record]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogIndex = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">整理 queue review AI summary Telegram dispatch report catalog index 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogIndexEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogIndex(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch report catalog index 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogWritePreflight = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const index = data.telegram_dispatch_report_catalog_index || {};
|
||
const operator = data.operator_telegram_dispatch_report_catalog_write_preflight || {};
|
||
const sections = data.telegram_dispatch_report_catalog_write_preflight_sections || [];
|
||
const promotion = data.promotion_gate || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`preflight=${data.telegram_dispatch_report_catalog_write_preflight_passed ? 'pass' : 'blocked'}`,
|
||
`record_write=${data.ready_for_market_intel_report_catalog_record_write ? 'ready' : 'blocked'}`,
|
||
`catalog=${data.catalog_record_written ? 'written' : 'blocked'}`,
|
||
`db=${data.database_write_executed ? 'written' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只檢查 Telegram dispatch report catalog write preflight;API/UI 不讀 token、不呼叫 LLM、不開 DB、不寫 catalog record、不產報表、不補發 Telegram、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">WRITE PREFLIGHT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 write preflight gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PREFLIGHT SECTIONS</p>
|
||
<div class="market-intel-check-list">${
|
||
sections.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml((item.facts || []).join(' / '))}</small>
|
||
</div>
|
||
<span>READY</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 preflight sections。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">CATALOG INDEX</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', index.provided],
|
||
['mode', index.mode || 'missing'],
|
||
['index_passed', index.catalog_index_passed],
|
||
['report_family', index.target_report_family || 'missing'],
|
||
['report_hash', index.report_output_hash || 'missing'],
|
||
['section_count', index.section_count || 0],
|
||
['preflight_ready', index.ready_for_market_intel_report_catalog_write_preflight]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR PREFLIGHT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['preflight_path', operator.report_catalog_write_preflight_artifact_path || 'missing'],
|
||
['index_path', operator.report_catalog_index_artifact_path_recorded],
|
||
['handoff_path', operator.report_catalog_handoff_artifact_path_recorded],
|
||
['output_path', operator.report_output_artifact_path_recorded],
|
||
['catalog_key', operator.catalog_record_key_recorded],
|
||
['catalog_family', operator.catalog_family_recorded],
|
||
['index_key', operator.catalog_index_key_recorded],
|
||
['record_schema', operator.catalog_record_schema_recorded],
|
||
['record_write_gate', operator.operator_confirmed_report_catalog_record_write_requires_separate_gate],
|
||
['runtime_clear', operator.operator_confirmed_no_token_in_report_catalog_write_preflight && operator.operator_confirmed_no_api_report_generation && operator.operator_confirmed_no_api_file_write && operator.operator_confirmed_no_api_catalog_record_write && operator.operator_confirmed_no_api_telegram_dispatch && operator.operator_confirmed_no_api_db_write && operator.operator_confirmed_no_llm_call && operator.operator_confirmed_no_scheduler_attach]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PROMOTION</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['allowed', promotion.allowed],
|
||
['next_phase', promotion.next_manual_phase || 'missing'],
|
||
['requires_real_db_write', promotion.requires_real_db_write],
|
||
['record_write_separate_gate', promotion.report_catalog_record_write_requires_separate_gate],
|
||
['commit_separate_gate', promotion.report_catalog_record_commit_requires_separate_gate],
|
||
['api_must_not_write_catalog_record', promotion.api_must_not_write_catalog_record]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogWritePreflight = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">檢查 queue review AI summary Telegram dispatch report catalog write preflight 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogWritePreflightEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogWritePreflight(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch report catalog write preflight 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordWrite = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const preflight = data.telegram_dispatch_report_catalog_write_preflight || {};
|
||
const operator = data.operator_telegram_dispatch_report_catalog_record_write || {};
|
||
const sections = data.telegram_dispatch_report_catalog_record_write_sections || [];
|
||
const promotion = data.promotion_gate || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`record_write=${data.telegram_dispatch_report_catalog_record_write_passed ? 'pass' : 'blocked'}`,
|
||
`cli=${data.ready_for_market_intel_report_catalog_record_cli_run ? 'ready' : 'blocked'}`,
|
||
`catalog=${data.catalog_record_written ? 'written' : 'blocked'}`,
|
||
`db=${data.database_write_executed ? 'written' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只檢查 Telegram dispatch report catalog record write gate;API/UI 不讀 token、不開 DB、不寫 catalog record、不產報表、不補發 Telegram、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RECORD WRITE GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 record write gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RECORD WRITE SECTIONS</p>
|
||
<div class="market-intel-check-list">${
|
||
sections.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml((item.facts || []).join(' / '))}</small>
|
||
</div>
|
||
<span>READY</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 record write sections。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">WRITE PREFLIGHT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', preflight.provided],
|
||
['mode', preflight.mode || 'missing'],
|
||
['preflight_passed', preflight.catalog_write_preflight_passed],
|
||
['record_write_ready', preflight.ready_for_market_intel_report_catalog_record_write],
|
||
['report_family', preflight.target_report_family || 'missing'],
|
||
['report_hash', preflight.report_output_hash || 'missing'],
|
||
['schema_recorded', preflight.catalog_record_schema_recorded]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR RECORD WRITE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['record_write_path', operator.report_catalog_record_write_artifact_path || 'missing'],
|
||
['preflight_path', operator.report_catalog_write_preflight_artifact_path_recorded],
|
||
['index_path', operator.report_catalog_index_artifact_path_recorded],
|
||
['output_path', operator.report_output_artifact_path_recorded],
|
||
['catalog_key', operator.catalog_record_key_recorded],
|
||
['record_schema', operator.catalog_record_schema_recorded],
|
||
['backup', operator.catalog_record_backup_artifact_path_recorded],
|
||
['dry_run', operator.catalog_record_write_dry_run_artifact_path_recorded],
|
||
['commit_gate', operator.operator_confirmed_report_catalog_record_commit_requires_separate_gate],
|
||
['runtime_clear', operator.operator_confirmed_no_token_in_report_catalog_record_write && operator.operator_confirmed_no_api_file_write && operator.operator_confirmed_no_api_catalog_record_write && operator.operator_confirmed_no_api_telegram_dispatch && operator.operator_confirmed_no_api_db_write && operator.operator_confirmed_no_llm_call && operator.operator_confirmed_no_scheduler_attach]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PROMOTION</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['allowed', promotion.allowed],
|
||
['next_phase', promotion.next_manual_phase || 'missing'],
|
||
['requires_cli_run', promotion.requires_cli_run],
|
||
['requires_real_db_write', promotion.requires_real_db_write],
|
||
['run_package_gate', promotion.report_catalog_record_run_package_requires_separate_gate],
|
||
['commit_gate', promotion.report_catalog_record_commit_requires_separate_gate],
|
||
['api_must_not_write_database', promotion.api_must_not_write_database]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordWrite = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">檢查 queue review AI summary Telegram dispatch report catalog record write gate 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordWriteEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordWrite(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch report catalog record write gate 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunPackage = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const recordWrite = data.telegram_dispatch_report_catalog_record_write || {};
|
||
const operator = data.operator_telegram_dispatch_report_catalog_record_run_package || {};
|
||
const sections = data.telegram_dispatch_report_catalog_record_run_package_sections || [];
|
||
const command = data.command_bundle || {};
|
||
const manifest = data.payload_manifest || {};
|
||
const promotion = data.promotion_gate || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`run_package=${data.telegram_dispatch_report_catalog_record_run_package_passed ? 'pass' : 'blocked'}`,
|
||
`readiness=${data.ready_for_market_intel_report_catalog_record_run_readiness ? 'ready' : 'blocked'}`,
|
||
`cli=${data.catalog_record_cli_executed ? 'executed' : 'blocked'}`,
|
||
`db=${data.database_write_executed ? 'written' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只產生 Telegram dispatch report catalog record run package 預覽;API/UI 不讀 token、不寫檔、不執行 CLI、不開 DB、不寫 catalog record、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RUN PACKAGE GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 run package gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RUN PACKAGE SECTIONS</p>
|
||
<div class="market-intel-check-list">${
|
||
sections.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml((item.facts || []).join(' / '))}</small>
|
||
</div>
|
||
<span>READY</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 run package sections。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RECORD WRITE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', recordWrite.provided],
|
||
['mode', recordWrite.mode || 'missing'],
|
||
['record_write_passed', recordWrite.catalog_record_write_passed],
|
||
['cli_ready', recordWrite.ready_for_market_intel_report_catalog_record_cli_run],
|
||
['report_family', recordWrite.target_report_family || 'missing'],
|
||
['report_hash', recordWrite.report_output_hash || 'missing'],
|
||
['dry_run', recordWrite.catalog_record_write_dry_run_artifact_path_recorded]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PACKAGE INPUT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['package_path', operator.report_catalog_record_run_package_artifact_path || 'missing'],
|
||
['write_gate_path', operator.report_catalog_record_write_artifact_path_recorded],
|
||
['manifest_path', operator.catalog_record_payload_manifest_path_recorded],
|
||
['backup', operator.catalog_record_backup_artifact_path_recorded],
|
||
['dry_run', operator.catalog_record_write_dry_run_artifact_path_recorded],
|
||
['cli_command', operator.catalog_record_cli_command_recorded],
|
||
['runtime_clear', operator.operator_confirmed_no_token_in_report_catalog_record_run_package && operator.operator_confirmed_no_api_file_write && operator.operator_confirmed_no_api_catalog_record_write && operator.operator_confirmed_no_api_telegram_dispatch && operator.operator_confirmed_no_api_db_write && operator.operator_confirmed_no_llm_call && operator.operator_confirmed_no_scheduler_attach]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">COMMAND / PROMOTION</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['script', command.script_path || 'missing'],
|
||
['api_must_not_execute', command.api_must_not_execute_command],
|
||
['manifest_execute_in_api', manifest.execute_in_api],
|
||
['next_phase', promotion.next_manual_phase || 'missing'],
|
||
['run_readiness_gate', promotion.report_catalog_record_run_readiness_requires_separate_gate],
|
||
['commit_gate', promotion.report_catalog_record_commit_requires_separate_gate]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunPackage = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">產生 queue review AI summary Telegram dispatch report catalog record run package 預覽中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunPackageEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunPackage(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch report catalog record run package 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunReadiness = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const runPackage = data.telegram_dispatch_report_catalog_record_run_package || {};
|
||
const operator = data.operator_telegram_dispatch_report_catalog_record_run_readiness || {};
|
||
const manifest = data.catalog_record_run_readiness_manifest || {};
|
||
const command = manifest.manual_cli_run_command || {};
|
||
const promotion = data.promotion_gate || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`readiness=${data.telegram_dispatch_report_catalog_record_run_readiness_passed ? 'pass' : 'blocked'}`,
|
||
`operator_cli=${data.ready_for_cli_operator_run ? 'ready' : 'blocked'}`,
|
||
`api_cli=${data.api_executes_cli ? 'executed' : 'blocked'}`,
|
||
`db=${data.database_write_executed ? 'written' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只檢查 Telegram dispatch report catalog record run readiness;API/UI 不讀 token、不寫檔、不執行 CLI、不開 DB、不寫 catalog record、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">READINESS GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 run readiness gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RUN PACKAGE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', runPackage.provided],
|
||
['mode', runPackage.mode || 'missing'],
|
||
['package_passed', runPackage.catalog_record_run_package_passed],
|
||
['record_family', runPackage.record_family || 'missing'],
|
||
['statement_count', runPackage.statement_count || 0],
|
||
['report_hash', runPackage.report_output_hash || 'missing']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR EVIDENCE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['readiness_path', operator.report_catalog_record_run_readiness_artifact_path || 'missing'],
|
||
['run_package_path', operator.report_catalog_record_run_package_artifact_path_recorded],
|
||
['manifest_path', operator.catalog_record_payload_manifest_path_recorded],
|
||
['backup', operator.catalog_record_backup_artifact_path_recorded],
|
||
['dry_run', operator.catalog_record_write_dry_run_artifact_path_recorded],
|
||
['receipt_path', operator.catalog_record_run_receipt_artifact_path_recorded],
|
||
['smoke_path', operator.catalog_record_postwrite_smoke_artifact_path_recorded]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">CLI BOUNDARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['manifest', manifest.manifest_version || 'missing'],
|
||
['script', command.script_path || 'missing'],
|
||
['executed_by_api', command.executed_by_api],
|
||
['approval_env', command.requires_approval_env_var || 'missing'],
|
||
['operator_shell_only', operator.operator_confirmed_one_time_token_shell_only],
|
||
['runtime_clear', operator.operator_confirmed_no_token_in_report_catalog_record_run_readiness && operator.operator_confirmed_no_api_file_write && operator.operator_confirmed_no_api_catalog_record_write && operator.operator_confirmed_no_api_telegram_dispatch && operator.operator_confirmed_no_api_db_write && operator.operator_confirmed_no_llm_call && operator.operator_confirmed_no_scheduler_attach]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PROMOTION</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['next_phase', promotion.next_manual_phase || 'missing'],
|
||
['requires_cli', promotion.requires_cli_run],
|
||
['requires_db_write', promotion.requires_real_db_write],
|
||
['receipt_gate', promotion.report_catalog_record_run_receipt_requires_separate_gate],
|
||
['commit_gate', promotion.report_catalog_record_commit_requires_separate_gate]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunReadiness = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">檢查 queue review AI summary Telegram dispatch report catalog record run readiness 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunReadinessEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunReadiness(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch report catalog record run readiness 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunReceipt = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const readiness = data.telegram_dispatch_report_catalog_record_run_readiness || {};
|
||
const writer = data.telegram_dispatch_report_catalog_record_run_receipt_writer || {};
|
||
const smoke = data.telegram_dispatch_report_catalog_record_run_receipt_postwrite_smoke || {};
|
||
const operator = data.operator_telegram_dispatch_report_catalog_record_run_receipt || {};
|
||
const promotion = data.promotion_gate || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`receipt=${data.telegram_dispatch_report_catalog_record_run_receipt_passed ? 'pass' : 'blocked'}`,
|
||
`writer=${writer.catalog_record_written ? 'written' : 'blocked'}`,
|
||
`smoke=${smoke.postwrite_smoke_passed ? 'pass' : 'blocked'}`,
|
||
`api_db=${data.api_writes_database ? 'written' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只審核外部 CLI 回貼的 Telegram dispatch report catalog record run receipt;API/UI 不讀 token、不寫檔、不執行 CLI、不開 DB、不寫 catalog record、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RECEIPT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 run receipt gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">READINESS</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', readiness.provided],
|
||
['mode', readiness.mode || 'missing'],
|
||
['passed', readiness.run_readiness_passed],
|
||
['record_family', readiness.record_family || 'missing'],
|
||
['statement_count', readiness.statement_count || 0],
|
||
['expected_hash', readiness.expected_summary_payload_hash || 'missing']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">WRITER OUTPUT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['mode', writer.mode || 'missing'],
|
||
['cli_executed', writer.catalog_record_cli_executed],
|
||
['record_written', writer.catalog_record_written],
|
||
['db_write', writer.database_write_executed],
|
||
['commit', writer.database_commit_executed],
|
||
['hash_match', writer.summary_payload_hash_matches_expected]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">POSTWRITE SMOKE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['mode', smoke.mode || 'missing'],
|
||
['read_only', smoke.read_only_query_executed],
|
||
['smoke_passed', smoke.postwrite_smoke_passed],
|
||
['record_found', smoke.catalog_record_found],
|
||
['hash_match', smoke.summary_payload_hash_matches_expected],
|
||
['db_write', smoke.database_write_executed]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['receipt_path', operator.report_catalog_record_run_receipt_artifact_path || 'missing'],
|
||
['readiness_path', operator.report_catalog_record_run_readiness_artifact_path_recorded],
|
||
['writer_path', operator.catalog_record_writer_output_artifact_path_recorded],
|
||
['smoke_path', operator.catalog_record_postwrite_smoke_artifact_path_recorded],
|
||
['commit_gate', operator.operator_confirmed_catalog_record_commit_requires_separate_gate],
|
||
['runtime_clear', operator.operator_confirmed_no_token_in_report_catalog_record_run_receipt && operator.operator_confirmed_no_api_file_write && operator.operator_confirmed_no_api_cli_execution && operator.operator_confirmed_no_api_catalog_record_write && operator.operator_confirmed_no_api_telegram_dispatch && operator.operator_confirmed_no_api_db_write && operator.operator_confirmed_no_llm_call && operator.operator_confirmed_no_scheduler_attach]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PROMOTION</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['next_phase', promotion.next_manual_phase || 'missing'],
|
||
['commit_gate', promotion.report_catalog_record_commit_requires_separate_gate],
|
||
['api_write', promotion.api_must_not_write_database],
|
||
['api_cli', promotion.api_must_not_execute_cli],
|
||
['api_catalog_record', promotion.api_must_not_write_catalog_record]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunReceipt = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">審核 queue review AI summary Telegram dispatch report catalog record run receipt 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunReceiptEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunReceipt(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch report catalog record run receipt 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordCommit = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const receipt = data.telegram_dispatch_report_catalog_record_run_receipt || {};
|
||
const operator = data.operator_telegram_dispatch_report_catalog_record_commit || {};
|
||
const promotion = data.promotion_gate || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`commit=${data.telegram_dispatch_report_catalog_record_commit_passed ? 'pass' : 'blocked'}`,
|
||
`receipt=${receipt.run_receipt_passed ? 'pass' : 'blocked'}`,
|
||
`writer_commit=${receipt.writer_database_commit_executed ? 'seen' : 'missing'}`,
|
||
`api_db=${data.api_writes_database ? 'written' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只審核 Telegram dispatch report catalog record commit gate;API/UI 不讀 token、不寫檔、不執行 CLI、不開 DB、不寫 catalog record、不執行 commit、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">COMMIT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 commit gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RUN RECEIPT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', receipt.provided],
|
||
['mode', receipt.mode || 'missing'],
|
||
['passed', receipt.run_receipt_passed],
|
||
['record_key', receipt.writer_catalog_record_key || 'missing'],
|
||
['statement_count', receipt.writer_statement_count || 0],
|
||
['hash_match', receipt.writer_hash_match]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">COMMIT EVIDENCE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['writer_db_write', receipt.writer_database_write_executed],
|
||
['writer_commit', receipt.writer_database_commit_executed],
|
||
['writer_rollback', receipt.writer_database_rollback_executed],
|
||
['smoke_read_only', receipt.smoke_read_only_query_executed],
|
||
['smoke_passed', receipt.smoke_postwrite_smoke_passed],
|
||
['smoke_db_write', receipt.smoke_database_write_executed]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['commit_path', operator.report_catalog_record_commit_artifact_path || 'missing'],
|
||
['receipt_path', operator.report_catalog_record_run_receipt_artifact_path_recorded],
|
||
['backup_path', operator.catalog_record_backup_artifact_path_recorded],
|
||
['commit_gate', operator.operator_confirmed_report_catalog_record_commit_gate],
|
||
['db_commit_seen', operator.operator_confirmed_catalog_record_db_commit_observed],
|
||
['closeout_gate', operator.operator_confirmed_catalog_record_closeout_requires_separate_gate]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RUNTIME</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['api_file', data.api_writes_file],
|
||
['api_cli', data.api_executes_cli],
|
||
['api_db', data.api_writes_database],
|
||
['db_write', data.database_write_executed],
|
||
['commit_exec', data.catalog_record_commit_executed],
|
||
['scheduler', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PROMOTION</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['next_phase', promotion.next_manual_phase || 'missing'],
|
||
['closeout_gate', promotion.report_catalog_record_closeout_requires_separate_gate],
|
||
['api_commit', promotion.api_must_not_commit_catalog_record],
|
||
['api_write', promotion.api_must_not_write_database],
|
||
['api_catalog_record', promotion.api_must_not_write_catalog_record]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordCommit = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">審核 queue review AI summary Telegram dispatch report catalog record commit gate 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordCommitEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordCommit(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch report catalog record commit gate 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordCloseout = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const commit = data.telegram_dispatch_report_catalog_record_commit || {};
|
||
const operator = data.operator_telegram_dispatch_report_catalog_record_closeout || {};
|
||
const promotion = data.promotion_gate || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`closeout=${data.telegram_dispatch_report_catalog_record_closeout_passed ? 'pass' : 'blocked'}`,
|
||
`commit=${commit.commit_passed ? 'pass' : 'blocked'}`,
|
||
`writer_commit=${commit.writer_database_commit_executed ? 'seen' : 'missing'}`,
|
||
`api_db=${data.api_writes_database ? 'written' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只審核 Telegram dispatch report catalog record closeout gate;API/UI 不讀 token、不寫檔、不執行 CLI、不開 DB、不寫 catalog record、不執行 commit、不派 Telegram、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">CLOSEOUT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 closeout gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">COMMIT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', commit.provided],
|
||
['mode', commit.mode || 'missing'],
|
||
['passed', commit.commit_passed],
|
||
['record_key', commit.writer_catalog_record_key || 'missing'],
|
||
['statement_count', commit.writer_statement_count || 0],
|
||
['hash_match', commit.writer_hash_match]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">COMMIT EVIDENCE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['db_commit', commit.writer_database_commit_executed],
|
||
['smoke_read_only', commit.smoke_read_only_query_executed],
|
||
['smoke_passed', commit.smoke_postwrite_smoke_passed],
|
||
['operator_commit', commit.operator_commit_gate],
|
||
['operator_db_seen', commit.operator_db_commit_observed],
|
||
['closeout_gate', commit.operator_closeout_requires_separate_gate]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['closeout_path', operator.report_catalog_record_closeout_artifact_path || 'missing'],
|
||
['commit_path', operator.report_catalog_record_commit_artifact_path_recorded],
|
||
['receipt_path', operator.report_catalog_record_run_receipt_artifact_path_recorded],
|
||
['backup_path', operator.catalog_record_backup_artifact_path_recorded],
|
||
['closeout_gate', operator.operator_confirmed_report_catalog_record_closeout_gate],
|
||
['archive_gate', operator.operator_confirmed_catalog_record_archive_requires_separate_gate]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RUNTIME</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['api_file', data.api_writes_file],
|
||
['api_cli', data.api_executes_cli],
|
||
['api_db', data.api_writes_database],
|
||
['db_write', data.database_write_executed],
|
||
['closeout_exec', data.catalog_record_closeout_executed],
|
||
['scheduler', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PROMOTION</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['next_phase', promotion.next_manual_phase || 'missing'],
|
||
['archive_gate', promotion.report_catalog_record_archive_requires_separate_gate],
|
||
['api_commit', promotion.api_must_not_commit_catalog_record],
|
||
['api_write', promotion.api_must_not_write_database],
|
||
['api_telegram', promotion.api_must_not_dispatch_telegram]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordCloseout = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">審核 queue review AI summary Telegram dispatch report catalog record closeout gate 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordCloseoutEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordCloseout(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch report catalog record closeout gate 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordArchive = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const closeout = data.telegram_dispatch_report_catalog_record_closeout || {};
|
||
const operator = data.operator_telegram_dispatch_report_catalog_record_archive || {};
|
||
const promotion = data.promotion_gate || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`archive=${data.telegram_dispatch_report_catalog_record_archive_passed ? 'pass' : 'blocked'}`,
|
||
`closeout=${closeout.closeout_passed ? 'pass' : 'blocked'}`,
|
||
`summary=${data.ready_for_market_intel_report_catalog_record_archive_summary ? 'ready' : 'blocked'}`,
|
||
`api_file=${data.api_writes_file ? 'written' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只審核 Telegram dispatch report catalog record archive gate;API/UI 不讀 token、不產報表、不寫檔、不執行 CLI、不開 DB、不寫 catalog record、不執行 commit、不派 Telegram、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">ARCHIVE GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 archive gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">CLOSEOUT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', closeout.provided],
|
||
['mode', closeout.mode || 'missing'],
|
||
['passed', closeout.closeout_passed],
|
||
['record_key', closeout.writer_catalog_record_key || 'missing'],
|
||
['archive_ready', closeout.ready_for_market_intel_report_catalog_record_archive],
|
||
['safe_boundaries', closeout.safe_boundaries_complete]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['archive_path', operator.report_catalog_record_archive_artifact_path || 'missing'],
|
||
['closeout_path', operator.report_catalog_record_closeout_artifact_path_recorded],
|
||
['commit_path', operator.report_catalog_record_commit_artifact_path_recorded],
|
||
['receipt_path', operator.report_catalog_record_run_receipt_artifact_path_recorded],
|
||
['archive_gate', operator.operator_confirmed_report_catalog_record_archive_gate],
|
||
['summary_gate', operator.operator_confirmed_catalog_record_archive_summary_requires_separate_gate]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RUNTIME</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['api_report', data.ready_for_report_generation],
|
||
['api_file', data.api_writes_file],
|
||
['api_cli', data.api_executes_cli],
|
||
['api_db', data.api_writes_database],
|
||
['archive_exec', data.catalog_record_archive_executed],
|
||
['scheduler', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PROMOTION</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['next_phase', promotion.next_manual_phase || 'missing'],
|
||
['summary_gate', promotion.report_catalog_record_archive_summary_requires_separate_gate],
|
||
['api_commit', promotion.api_must_not_commit_catalog_record],
|
||
['api_write', promotion.api_must_not_write_database],
|
||
['api_telegram', promotion.api_must_not_dispatch_telegram]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordArchive = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">審核 queue review AI summary Telegram dispatch report catalog record archive gate 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordArchiveEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordArchive(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch report catalog record archive gate 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordArchiveSummary = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const archive = data.telegram_dispatch_report_catalog_record_archive || {};
|
||
const operator = data.operator_telegram_dispatch_report_catalog_record_archive_summary || {};
|
||
const promotion = data.promotion_gate || {};
|
||
const sections = data.telegram_dispatch_report_catalog_record_archive_summary_sections || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`summary=${data.telegram_dispatch_report_catalog_record_archive_summary_passed ? 'pass' : 'blocked'}`,
|
||
`archive=${archive.archive_passed ? 'pass' : 'blocked'}`,
|
||
`final=${data.ready_for_market_intel_report_catalog_record_final_closeout ? 'ready' : 'blocked'}`,
|
||
`file=${data.catalog_record_archive_summary_file_written ? 'written' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只審核 Telegram dispatch report catalog record archive summary gate;API/UI 不讀 token、不產報表、不寫檔、不執行 CLI、不開 DB、不寫 catalog record、不執行 commit、不派 Telegram、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">SUMMARY GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 archive summary gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">ARCHIVE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', archive.provided],
|
||
['mode', archive.mode || 'missing'],
|
||
['passed', archive.archive_passed],
|
||
['record_key', archive.writer_catalog_record_key || 'missing'],
|
||
['summary_ready', archive.ready_for_market_intel_report_catalog_record_archive_summary],
|
||
['safe_boundaries', archive.safe_boundaries_complete]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">SECTIONS</p>
|
||
<div class="market-intel-operation-list">${
|
||
sections.map(section => `
|
||
<article class="market-intel-operation">
|
||
<strong>${escapeHtml(section.key || 'section')}</strong>
|
||
<small>${escapeHtml((section.facts || []).join(' / '))}</small>
|
||
</article>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 summary sections。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['summary_path', operator.report_catalog_record_archive_summary_artifact_path || 'missing'],
|
||
['archive_path', operator.report_catalog_record_archive_artifact_path_recorded],
|
||
['closeout_path', operator.report_catalog_record_closeout_artifact_path_recorded],
|
||
['traceability', operator.operator_confirmed_catalog_record_traceability_reviewed],
|
||
['summary_gate', operator.operator_confirmed_report_catalog_record_archive_summary_gate],
|
||
['final_gate', operator.operator_confirmed_catalog_record_final_closeout_requires_separate_gate]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RUNTIME</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['api_report', data.ready_for_report_generation],
|
||
['api_file', data.api_writes_file],
|
||
['api_cli', data.api_executes_cli],
|
||
['api_db', data.api_writes_database],
|
||
['summary_exec', data.catalog_record_archive_summary_executed],
|
||
['scheduler', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PROMOTION</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['next_phase', promotion.next_manual_phase || 'missing'],
|
||
['final_gate', promotion.report_catalog_record_final_closeout_requires_separate_gate],
|
||
['api_commit', promotion.api_must_not_commit_catalog_record],
|
||
['api_write', promotion.api_must_not_write_database],
|
||
['api_telegram', promotion.api_must_not_dispatch_telegram]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordArchiveSummary = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">審核 queue review AI summary Telegram dispatch report catalog record archive summary gate 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordArchiveSummaryEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordArchiveSummary(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch report catalog record archive summary gate 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordFinalCloseout = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const summary = data.telegram_dispatch_report_catalog_record_archive_summary || {};
|
||
const operator = data.operator_telegram_dispatch_report_catalog_record_final_closeout || {};
|
||
const promotion = data.promotion_gate || {};
|
||
const sections = data.telegram_dispatch_report_catalog_record_final_closeout_sections || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`closeout=${data.telegram_dispatch_report_catalog_record_final_closeout_passed ? 'pass' : 'blocked'}`,
|
||
`summary=${summary.archive_summary_passed ? 'pass' : 'blocked'}`,
|
||
`complete=${data.market_intel_report_catalog_record_pipeline_complete ? 'yes' : 'blocked'}`,
|
||
`file=${data.catalog_record_final_closeout_file_written ? 'written' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只審核 Telegram dispatch report catalog record final closeout gate;API/UI 不讀 token、不產報表、不寫檔、不執行 CLI、不開 DB、不寫 catalog record、不執行 commit、不派 Telegram、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">FINAL GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 final closeout gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">ARCHIVE SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', summary.provided],
|
||
['mode', summary.mode || 'missing'],
|
||
['passed', summary.archive_summary_passed],
|
||
['record_key', summary.writer_catalog_record_key || 'missing'],
|
||
['sections', summary.summary_sections_complete],
|
||
['safe_boundaries', summary.safe_boundaries_complete]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">SECTIONS</p>
|
||
<div class="market-intel-operation-list">${
|
||
sections.map(section => `
|
||
<article class="market-intel-operation">
|
||
<strong>${escapeHtml(section.key || 'section')}</strong>
|
||
<small>${escapeHtml((section.facts || []).join(' / '))}</small>
|
||
</article>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 final closeout sections。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['final_path', operator.report_catalog_record_final_closeout_artifact_path || 'missing'],
|
||
['summary_path', operator.report_catalog_record_archive_summary_artifact_path_recorded],
|
||
['traceability', operator.operator_confirmed_catalog_record_traceability_reviewed],
|
||
['pipeline', operator.operator_confirmed_catalog_record_pipeline_complete],
|
||
['no_followup', operator.operator_confirmed_no_pending_catalog_record_followup],
|
||
['read_only', operator.operator_confirmed_catalog_record_final_closeout_read_only]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RUNTIME</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['api_report', data.ready_for_report_generation],
|
||
['api_file', data.api_writes_file],
|
||
['api_cli', data.api_executes_cli],
|
||
['api_db', data.api_writes_database],
|
||
['final_exec', data.catalog_record_final_closeout_executed],
|
||
['scheduler', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PROMOTION</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['next_phase', promotion.next_manual_phase || 'missing'],
|
||
['terminal', promotion.terminal_gate],
|
||
['api_commit', promotion.api_must_not_commit_catalog_record],
|
||
['api_write', promotion.api_must_not_write_database],
|
||
['api_telegram', promotion.api_must_not_dispatch_telegram]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordFinalCloseout = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">審核 queue review AI summary Telegram dispatch report catalog record final closeout gate 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordFinalCloseoutEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordFinalCloseout(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch report catalog record final closeout gate 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewDecisionWriter = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const summary = data.statement_summary || {};
|
||
const command = data.command_bundle || {};
|
||
const gates = data.approval_gates || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`ready=${data.ready_for_real_write ? 'yes' : 'no'}`,
|
||
`statements=${summary.statement_count || 0}`,
|
||
`api_update=${data.api_updates_review_state ? 'yes' : 'no'}`,
|
||
`impl=${data.writer_implementation_enabled ? 'enabled' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只檢查 queue review decision writer CLI gate;API/UI 不讀 token、不執行 CLI、不連 DB、不更新 review_state。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">WRITER GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 writer gates。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">COMMAND SHAPE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['script_path', data.script_path || 'unknown'],
|
||
['env_var', data.approval_env_var || 'unknown'],
|
||
['dry_run', command.dry_run_command || 'not_ready'],
|
||
['api_must_not_execute', command.api_must_not_execute_command]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">WRITE FLAGS</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['execute_requested', data.execute_requested],
|
||
['apply_real_write', data.apply_real_write_requested],
|
||
['token_present', data.approval_token_present],
|
||
['token_valid', data.approval_token_valid],
|
||
['db_write', data.database_write_executed],
|
||
['commit', data.database_commit_executed],
|
||
['scheduler', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">STATEMENTS</p>
|
||
<div class="market-intel-check-list">${
|
||
(summary.review_state_updates || []).map(row => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(row.dedupe_key || row.idempotency_key || 'unknown')}</strong>
|
||
<small>${escapeHtml(row.expected_current_review_state || 'unknown')} -> ${escapeHtml(row.next_review_state || 'none')}</small>
|
||
</div>
|
||
<span>${escapeHtml(row.write_status || 'blocked')}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 review_state update statements。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewDecisionWriter = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">檢查 queue review decision writer CLI gate 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewDecisionWriterEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewDecisionWriter(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review decision writer CLI gate 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderSchedulerMeta = data => {
|
||
schedulerMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`ready=${data.ready_to_attach_scheduler ? 'yes' : 'no'}`,
|
||
`jobs=${data.job_count || 0}`,
|
||
`scheduler=${data.scheduler_attached ? 'on' : 'off'}`,
|
||
`crawler=${data.crawler_job_started ? 'started' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderSchedulerBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const checks = Object.entries(data.gate_checks || {});
|
||
const jobs = data.jobs || [];
|
||
const sequence = data.attach_sequence || [];
|
||
const fallback = data.fallback_plan || [];
|
||
const renderCheck = ([name, passed]) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(name)}</strong>
|
||
</div>
|
||
<span>${passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`;
|
||
const renderJob = item => `
|
||
<article class="market-intel-operation">
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)} / ${escapeHtml(item.cadence)}</small>
|
||
<small>${escapeHtml(item.entrypoint)} / max=${escapeHtml(item.max_runtime_minutes)}m</small>
|
||
<small>network=${item.requires_external_network ? 'yes' : 'no'} / write=${item.requires_database_write ? 'yes' : 'no'}</small>
|
||
</article>
|
||
`;
|
||
const renderPlanItem = (item, status) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key || status)}</strong>
|
||
<small>${escapeHtml(item.label || item)}</small>
|
||
</div>
|
||
<span>${escapeHtml(status).toUpperCase()}</span>
|
||
</div>
|
||
`;
|
||
schedulerBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">這是排程掛載計畫,不會從 API 註冊 job、不啟動 crawler、不連外、不寫 DB。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-scheduler-checks>
|
||
<p class="market-intel-deploy-section-title">SCHEDULER GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
checks.length
|
||
? checks.map(renderCheck).join('')
|
||
: '<div class="market-intel-empty">尚未提供排程 gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-scheduler-jobs>
|
||
<p class="market-intel-deploy-section-title">PLANNED JOBS</p>
|
||
<div class="market-intel-operation-list">${
|
||
jobs.length
|
||
? jobs.map(renderJob).join('')
|
||
: '<div class="market-intel-empty">尚未提供排程 job。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-scheduler-sequence>
|
||
<p class="market-intel-deploy-section-title">ATTACH SEQUENCE</p>
|
||
<div class="market-intel-check-list">${
|
||
sequence.length
|
||
? sequence.map(item => renderPlanItem(item, 'required')).join('')
|
||
: '<div class="market-intel-empty">尚未提供掛載順序。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-scheduler-fallback>
|
||
<p class="market-intel-deploy-section-title">FALLBACK</p>
|
||
<div class="market-intel-check-list">${
|
||
fallback.length
|
||
? fallback.map(item => renderPlanItem(item, 'ready')).join('')
|
||
: '<div class="market-intel-empty">尚未提供備援方案。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadScheduler = async () => {
|
||
if (!schedulerMeta || !schedulerBody) return;
|
||
schedulerBody.innerHTML = '<div class="market-intel-empty">讀取排程掛載計畫中...</div>';
|
||
try {
|
||
const response = await fetch(schedulerEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderSchedulerMeta(data);
|
||
renderSchedulerBody(data);
|
||
} catch (error) {
|
||
schedulerMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
schedulerBody.innerHTML = `<div class="market-intel-empty">排程掛載計畫讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderMatchReviewMeta = data => {
|
||
const thresholds = data.thresholds || {};
|
||
matchReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`ready=${data.ready_for_review_queue ? 'yes' : 'no'}`,
|
||
`signals=${(data.scoring_signals || []).length}`,
|
||
`auto_confirm=${thresholds.auto_confirm_enabled ? 'on' : 'off'}`,
|
||
`writes=${data.writes_executed ? 'executed' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderMatchReviewBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const checks = Object.entries(data.gate_checks || {});
|
||
const signals = data.scoring_signals || [];
|
||
const actions = data.review_actions || [];
|
||
const sequence = data.operator_sequence || [];
|
||
const renderCheck = ([name, passed]) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(name)}</strong>
|
||
</div>
|
||
<span>${passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`;
|
||
const renderSignal = item => `
|
||
<article class="market-intel-operation">
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
<small>weight=${escapeHtml(item.weight)} / ${escapeHtml(item.source)}</small>
|
||
</article>
|
||
`;
|
||
const renderAction = item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.requires_operator ? 'HITL' : 'AUTO'}</span>
|
||
</div>
|
||
`;
|
||
matchReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">商品比對目前只建立審核規則與 HITL 流程;不建立 queue、不自動確認、不寫 DB、不呼叫外部 MCP。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-match-review-checks>
|
||
<p class="market-intel-deploy-section-title">MATCH GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
checks.length
|
||
? checks.map(renderCheck).join('')
|
||
: '<div class="market-intel-empty">尚未提供比對 gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-match-review-signals>
|
||
<p class="market-intel-deploy-section-title">SCORING SIGNALS</p>
|
||
<div class="market-intel-operation-list">${
|
||
signals.length
|
||
? signals.map(renderSignal).join('')
|
||
: '<div class="market-intel-empty">尚未提供比對訊號。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-match-review-actions>
|
||
<p class="market-intel-deploy-section-title">REVIEW ACTIONS</p>
|
||
<div class="market-intel-check-list">${
|
||
actions.length
|
||
? actions.map(renderAction).join('')
|
||
: '<div class="market-intel-empty">尚未提供審核動作。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-match-review-sequence>
|
||
<p class="market-intel-deploy-section-title">OPERATOR SEQUENCE</p>
|
||
<div class="market-intel-check-list">${
|
||
sequence.length
|
||
? sequence.map((item, index) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(`step_${index + 1}`)}</strong>
|
||
<small>${escapeHtml(item)}</small>
|
||
</div>
|
||
<span>REQUIRED</span>
|
||
</div>
|
||
`).join('')
|
||
: '<div class="market-intel-empty">尚未提供操作順序。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadMatchReview = async () => {
|
||
if (!matchReviewMeta || !matchReviewBody) return;
|
||
matchReviewBody.innerHTML = '<div class="market-intel-empty">讀取商品比對審核計畫中...</div>';
|
||
try {
|
||
const response = await fetch(matchReviewEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderMatchReviewMeta(data);
|
||
renderMatchReviewBody(data);
|
||
} catch (error) {
|
||
matchReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
matchReviewBody.innerHTML = `<div class="market-intel-empty">商品比對審核計畫讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderOpportunityMeta = data => {
|
||
opportunityMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`ready=${data.ready_for_opportunity_queue ? 'yes' : 'no'}`,
|
||
`rules=${data.rule_count || 0}`,
|
||
`alerts=${data.threat_alert_dispatched ? 'sent' : 'blocked'}`,
|
||
`ai=${data.ai_summary_generated ? 'generated' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderOpportunityBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const checks = Object.entries(data.gate_checks || {});
|
||
const rules = data.rules || [];
|
||
const sequence = data.operator_sequence || [];
|
||
const severity = Object.entries(data.severity_policy || {});
|
||
const renderCheck = ([name, passed]) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(name)}</strong>
|
||
</div>
|
||
<span>${passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`;
|
||
const renderRule = item => `
|
||
<article class="market-intel-operation">
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)} / severity=${escapeHtml(item.severity)}</small>
|
||
<small>${escapeHtml((item.minimum_evidence || []).join(' + '))}</small>
|
||
<small>${escapeHtml(item.action_hint)}</small>
|
||
</article>
|
||
`;
|
||
opportunityBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">目前只建立機會與威脅判讀規則;不建立 queue、不派送 Telegram、不產生 AI 摘要、不寫 DB。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-opportunity-checks>
|
||
<p class="market-intel-deploy-section-title">OPPORTUNITY GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
checks.length
|
||
? checks.map(renderCheck).join('')
|
||
: '<div class="market-intel-empty">尚未提供機會判讀 gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-opportunity-rules>
|
||
<p class="market-intel-deploy-section-title">RULES</p>
|
||
<div class="market-intel-operation-list">${
|
||
rules.length
|
||
? rules.map(renderRule).join('')
|
||
: '<div class="market-intel-empty">尚未提供規則。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-opportunity-severity>
|
||
<p class="market-intel-deploy-section-title">SEVERITY POLICY</p>
|
||
<div class="market-intel-check-list">${
|
||
severity.length
|
||
? severity.map(([key, label]) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(key)}</strong>
|
||
<small>${escapeHtml(label)}</small>
|
||
</div>
|
||
<span>POLICY</span>
|
||
</div>
|
||
`).join('')
|
||
: '<div class="market-intel-empty">尚未提供分級策略。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-opportunity-sequence>
|
||
<p class="market-intel-deploy-section-title">OPERATOR SEQUENCE</p>
|
||
<div class="market-intel-check-list">${
|
||
sequence.length
|
||
? sequence.map((item, index) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(`step_${index + 1}`)}</strong>
|
||
<small>${escapeHtml(item)}</small>
|
||
</div>
|
||
<span>REQUIRED</span>
|
||
</div>
|
||
`).join('')
|
||
: '<div class="market-intel-empty">尚未提供操作順序。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadOpportunity = async () => {
|
||
if (!opportunityMeta || !opportunityBody) return;
|
||
opportunityBody.innerHTML = '<div class="market-intel-empty">讀取市場機會與威脅計畫中...</div>';
|
||
try {
|
||
const response = await fetch(opportunityEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderOpportunityMeta(data);
|
||
renderOpportunityBody(data);
|
||
} catch (error) {
|
||
opportunityMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
opportunityBody.innerHTML = `<div class="market-intel-empty">市場機會與威脅計畫讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderOpportunityScoringMeta = data => {
|
||
opportunityScoringMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`ready=${data.ready_for_scoring_job ? 'yes' : 'no'}`,
|
||
`dimensions=${data.dimension_count || 0}`,
|
||
`weight=${data.total_weight || 0}`,
|
||
`score=${data.score_calculation_executed ? 'executed' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderOpportunityScoringBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const checks = Object.entries(data.gate_checks || {});
|
||
const dimensions = data.dimensions || [];
|
||
const thresholds = data.thresholds || [];
|
||
const evidenceTables = data.required_evidence_tables || [];
|
||
const sequence = data.operator_sequence || [];
|
||
const renderCheck = ([name, passed]) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(name)}</strong>
|
||
</div>
|
||
<span>${passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`;
|
||
const renderDimension = item => `
|
||
<article class="market-intel-operation">
|
||
<strong>${escapeHtml(item.key)} / ${escapeHtml(item.weight)}%</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
<small>${escapeHtml((item.evidence || []).join(' + '))}</small>
|
||
<small>${escapeHtml(item.calculation)}</small>
|
||
</article>
|
||
`;
|
||
opportunityScoringBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">目前只定義機會與威脅分數模型;不計分、不建立 queue、不派送 Telegram、不產生 AI 摘要、不寫 DB。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-opportunity-scoring-checks>
|
||
<p class="market-intel-deploy-section-title">SCORING GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
checks.length
|
||
? checks.map(renderCheck).join('')
|
||
: '<div class="market-intel-empty">尚未提供分數 gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-opportunity-scoring-dimensions>
|
||
<p class="market-intel-deploy-section-title">DIMENSIONS</p>
|
||
<div class="market-intel-operation-list">${
|
||
dimensions.length
|
||
? dimensions.map(renderDimension).join('')
|
||
: '<div class="market-intel-empty">尚未提供分數欄位。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-opportunity-scoring-thresholds>
|
||
<p class="market-intel-deploy-section-title">THRESHOLDS</p>
|
||
<div class="market-intel-check-list">${
|
||
thresholds.length
|
||
? thresholds.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.level)}</strong>
|
||
<small>${escapeHtml(item.action)}</small>
|
||
</div>
|
||
<span>${escapeHtml(item.minimum_score)}+</span>
|
||
</div>
|
||
`).join('')
|
||
: '<div class="market-intel-empty">尚未提供門檻。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-opportunity-scoring-evidence>
|
||
<p class="market-intel-deploy-section-title">EVIDENCE TABLES</p>
|
||
<div class="market-intel-check-list">${
|
||
evidenceTables.length
|
||
? evidenceTables.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item)}</strong>
|
||
</div>
|
||
<span>REQUIRED</span>
|
||
</div>
|
||
`).join('')
|
||
: '<div class="market-intel-empty">尚未提供 evidence tables。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-opportunity-scoring-sequence>
|
||
<p class="market-intel-deploy-section-title">OPERATOR SEQUENCE</p>
|
||
<div class="market-intel-check-list">${
|
||
sequence.length
|
||
? sequence.map((item, index) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(`step_${index + 1}`)}</strong>
|
||
<small>${escapeHtml(item)}</small>
|
||
</div>
|
||
<span>REQUIRED</span>
|
||
</div>
|
||
`).join('')
|
||
: '<div class="market-intel-empty">尚未提供操作順序。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadOpportunityScoring = async () => {
|
||
if (!opportunityScoringMeta || !opportunityScoringBody) return;
|
||
opportunityScoringBody.innerHTML = '<div class="market-intel-empty">讀取機會威脅分數模型中...</div>';
|
||
try {
|
||
const response = await fetch(opportunityScoringEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderOpportunityScoringMeta(data);
|
||
renderOpportunityScoringBody(data);
|
||
} catch (error) {
|
||
opportunityScoringMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
opportunityScoringBody.innerHTML = `<div class="market-intel-empty">機會威脅分數模型讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderOpportunityEvidenceMeta = data => {
|
||
opportunityEvidenceMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`ready=${data.ready_for_evidence_bundle ? 'yes' : 'no'}`,
|
||
`sections=${data.section_count || 0}`,
|
||
`query=${data.evidence_query_executed ? 'yes' : 'no'}`,
|
||
`bundle=${data.evidence_bundle_created ? 'created' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderOpportunityEvidenceBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const checks = Object.entries(data.gate_checks || {});
|
||
const sections = data.sections || [];
|
||
const gates = data.escalation_gates || [];
|
||
const tables = data.required_market_tables || [];
|
||
const sequence = data.operator_sequence || [];
|
||
const contract = data.bundle_contract || {};
|
||
const renderCheck = ([name, passed]) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(name)}</strong>
|
||
</div>
|
||
<span>${passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`;
|
||
const renderSection = item => `
|
||
<article class="market-intel-operation">
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
<small>${escapeHtml((item.source_tables || []).join(' + '))}</small>
|
||
<small>${escapeHtml((item.required_fields || []).join(' / '))}</small>
|
||
</article>
|
||
`;
|
||
opportunityEvidenceBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">目前只定義 evidence bundle contract;不查 DB、不產生 sample evidence、不建立 alert candidate、不派送 Telegram、不產生 AI 摘要。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-empty mb-3">dedupe=${escapeHtml(contract.dedupe_key || '')} / freshness=${escapeHtml(contract.freshness_window_hours || 'n/a')}h</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-opportunity-evidence-checks>
|
||
<p class="market-intel-deploy-section-title">EVIDENCE GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
checks.length
|
||
? checks.map(renderCheck).join('')
|
||
: '<div class="market-intel-empty">尚未提供 evidence gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-opportunity-evidence-sections>
|
||
<p class="market-intel-deploy-section-title">BUNDLE SECTIONS</p>
|
||
<div class="market-intel-operation-list">${
|
||
sections.length
|
||
? sections.map(renderSection).join('')
|
||
: '<div class="market-intel-empty">尚未提供 evidence section。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-opportunity-evidence-escalation>
|
||
<p class="market-intel-deploy-section-title">ESCALATION GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.length
|
||
? gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${escapeHtml((item.required_for || []).join('/'))}</span>
|
||
</div>
|
||
`).join('')
|
||
: '<div class="market-intel-empty">尚未提供升級 gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-opportunity-evidence-tables>
|
||
<p class="market-intel-deploy-section-title">REQUIRED TABLES</p>
|
||
<div class="market-intel-check-list">${
|
||
tables.length
|
||
? tables.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item)}</strong>
|
||
</div>
|
||
<span>REQUIRED</span>
|
||
</div>
|
||
`).join('')
|
||
: '<div class="market-intel-empty">尚未提供 required tables。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-opportunity-evidence-sequence>
|
||
<p class="market-intel-deploy-section-title">OPERATOR SEQUENCE</p>
|
||
<div class="market-intel-check-list">${
|
||
sequence.length
|
||
? sequence.map((item, index) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(`step_${index + 1}`)}</strong>
|
||
<small>${escapeHtml(item)}</small>
|
||
</div>
|
||
<span>REQUIRED</span>
|
||
</div>
|
||
`).join('')
|
||
: '<div class="market-intel-empty">尚未提供操作順序。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadOpportunityEvidence = async () => {
|
||
if (!opportunityEvidenceMeta || !opportunityEvidenceBody) return;
|
||
opportunityEvidenceBody.innerHTML = '<div class="market-intel-empty">讀取機會威脅證據包中...</div>';
|
||
try {
|
||
const response = await fetch(opportunityEvidenceEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderOpportunityEvidenceMeta(data);
|
||
renderOpportunityEvidenceBody(data);
|
||
} catch (error) {
|
||
opportunityEvidenceMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
opportunityEvidenceBody.innerHTML = `<div class="market-intel-empty">機會威脅證據包讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderOpportunityAlertMeta = data => {
|
||
opportunityAlertMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`ready=${data.ready_for_alert_candidates ? 'yes' : 'no'}`,
|
||
`channels=${data.channel_count || 0}`,
|
||
`review=${data.review_state_count || 0}`,
|
||
`queue_contract=${data.review_queue_contract_defined ? 'defined' : 'missing'}`,
|
||
`telegram=${data.telegram_dispatched ? 'sent' : 'blocked'}`,
|
||
`llm=${data.llm_call_executed ? 'called' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderOpportunityAlertBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const checks = Object.entries(data.gate_checks || {});
|
||
const channels = data.channels || [];
|
||
const gates = data.alert_gates || [];
|
||
const throttle = data.throttle_policy || {};
|
||
const reviewStates = data.review_states || [];
|
||
const reviewActions = data.review_actions || [];
|
||
const queueContract = data.review_queue_contract || {};
|
||
const queueIndexes = data.review_queue_indexes || [];
|
||
const priorityLanes = data.review_priority_lanes || [];
|
||
const approval = data.approval_policy || {};
|
||
const sequence = data.operator_sequence || [];
|
||
const payload = data.payload_contract || {};
|
||
const renderCheck = ([name, passed]) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(name)}</strong>
|
||
</div>
|
||
<span>${passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`;
|
||
const renderChannel = item => `
|
||
<article class="market-intel-operation">
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)} / minimum=${escapeHtml(item.minimum_level)}</small>
|
||
<small>approval=${item.requires_operator_approval ? 'required' : 'not_required'} / target=${escapeHtml(item.dispatch_target)}</small>
|
||
</article>
|
||
`;
|
||
const renderReviewState = item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)} / ${escapeHtml(item.description)}</small>
|
||
</div>
|
||
<span>${item.dispatch_allowed ? 'DISPATCH' : 'BLOCK'}</span>
|
||
</div>
|
||
`;
|
||
const renderReviewAction = item => `
|
||
<article class="market-intel-operation">
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml((item.from || []).join(' / '))} → ${escapeHtml(item.to)}</small>
|
||
<small>reason=${item.requires_reason ? 'required' : 'optional'}</small>
|
||
</article>
|
||
`;
|
||
const renderPriorityLane = item => `
|
||
<article class="market-intel-operation">
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)} / threshold=${escapeHtml(item.minimum_threshold)}</small>
|
||
<small>max_age=${escapeHtml(item.max_age_hours)}h</small>
|
||
</article>
|
||
`;
|
||
const renderQueueIndex = item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml((item.columns || []).join(' / '))} · ${escapeHtml(item.purpose)}</small>
|
||
</div>
|
||
<span>PLANNED</span>
|
||
</div>
|
||
`;
|
||
opportunityAlertBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">目前只定義告警候選、人工審核流程與審核佇列資料契約;不建立 alert queue、不建立 review queue、不建立 review table、不執行審核動作、不派送 Telegram、不呼叫 LLM、不寫 DB。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-empty mb-3">dedupe=${escapeHtml(throttle.dedupe_window_hours || 'n/a')}h / telegram_daily=${escapeHtml(throttle.max_telegram_candidates_per_day || 0)} / ai_digest=${escapeHtml(throttle.max_ai_briefing_items_per_digest || 0)}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-opportunity-alert-checks>
|
||
<p class="market-intel-deploy-section-title">ALERT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
checks.length
|
||
? checks.map(renderCheck).join('')
|
||
: '<div class="market-intel-empty">尚未提供告警 gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-opportunity-alert-channels>
|
||
<p class="market-intel-deploy-section-title">CHANNELS</p>
|
||
<div class="market-intel-operation-list">${
|
||
channels.length
|
||
? channels.map(renderChannel).join('')
|
||
: '<div class="market-intel-empty">尚未提供 channel。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-opportunity-alert-escalation>
|
||
<p class="market-intel-deploy-section-title">ESCALATION</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.length
|
||
? gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${escapeHtml((item.required_for || []).join('/'))}</span>
|
||
</div>
|
||
`).join('')
|
||
: '<div class="market-intel-empty">尚未提供升級 gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-opportunity-alert-payload>
|
||
<p class="market-intel-deploy-section-title">PAYLOAD CONTRACT</p>
|
||
<div class="market-intel-check-list">
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>required_fields</strong>
|
||
<small>${escapeHtml((payload.required_fields || []).join(' / '))}</small>
|
||
</div>
|
||
<span>REQUIRED</span>
|
||
</div>
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>forbidden_fields</strong>
|
||
<small>${escapeHtml((payload.forbidden_fields || []).join(' / '))}</small>
|
||
</div>
|
||
<span>BLOCKED</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-opportunity-alert-review>
|
||
<p class="market-intel-deploy-section-title">REVIEW STATES</p>
|
||
<div class="market-intel-check-list">${
|
||
reviewStates.length
|
||
? reviewStates.map(renderReviewState).join('')
|
||
: '<div class="market-intel-empty">尚未提供審核狀態。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-opportunity-alert-actions>
|
||
<p class="market-intel-deploy-section-title">REVIEW ACTIONS</p>
|
||
<div class="market-intel-operation-list">${
|
||
reviewActions.length
|
||
? reviewActions.map(renderReviewAction).join('')
|
||
: '<div class="market-intel-empty">尚未提供審核操作。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-opportunity-alert-queue-contract>
|
||
<p class="market-intel-deploy-section-title">REVIEW QUEUE CONTRACT</p>
|
||
<div class="market-intel-check-list">
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(queueContract.table_name || 'market_alert_review_queue')}</strong>
|
||
<small>pk=${escapeHtml(queueContract.primary_key || 'id')}</small>
|
||
</div>
|
||
<span>PREVIEW</span>
|
||
</div>
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>required_fields</strong>
|
||
<small>${escapeHtml((queueContract.required_fields || []).join(' / '))}</small>
|
||
</div>
|
||
<span>REQUIRED</span>
|
||
</div>
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>audit_fields</strong>
|
||
<small>${escapeHtml((queueContract.audit_fields || []).join(' / '))}</small>
|
||
</div>
|
||
<span>AUDIT</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-opportunity-alert-priority-lanes>
|
||
<p class="market-intel-deploy-section-title">PRIORITY LANES</p>
|
||
<div class="market-intel-operation-list">${
|
||
priorityLanes.length
|
||
? priorityLanes.map(renderPriorityLane).join('')
|
||
: '<div class="market-intel-empty">尚未提供優先級 lane。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-opportunity-alert-queue-indexes>
|
||
<p class="market-intel-deploy-section-title">QUEUE INDEXES</p>
|
||
<div class="market-intel-check-list">${
|
||
queueIndexes.length
|
||
? queueIndexes.map(renderQueueIndex).join('')
|
||
: '<div class="market-intel-empty">尚未提供索引規劃。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-opportunity-alert-approval>
|
||
<p class="market-intel-deploy-section-title">APPROVAL POLICY</p>
|
||
<div class="market-intel-check-list">${
|
||
Object.entries(approval).map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(key)}</strong>
|
||
</div>
|
||
<span>${value ? 'REQUIRED' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('')
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-opportunity-alert-sequence>
|
||
<p class="market-intel-deploy-section-title">OPERATOR SEQUENCE</p>
|
||
<div class="market-intel-check-list">${
|
||
sequence.length
|
||
? sequence.map((item, index) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(`step_${index + 1}`)}</strong>
|
||
<small>${escapeHtml(item)}</small>
|
||
</div>
|
||
<span>REQUIRED</span>
|
||
</div>
|
||
`).join('')
|
||
: '<div class="market-intel-empty">尚未提供操作順序。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadOpportunityAlert = async () => {
|
||
if (!opportunityAlertMeta || !opportunityAlertBody) return;
|
||
opportunityAlertBody.innerHTML = '<div class="market-intel-empty">讀取機會威脅告警候選中...</div>';
|
||
try {
|
||
const response = await fetch(opportunityAlertEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderOpportunityAlertMeta(data);
|
||
renderOpportunityAlertBody(data);
|
||
} catch (error) {
|
||
opportunityAlertMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
opportunityAlertBody.innerHTML = `<div class="market-intel-empty">機會威脅告警候選讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderMigrationMeta = data => {
|
||
const seedWriter = data.command_plan && data.command_plan.seed_writer_command
|
||
? data.command_plan.seed_writer_command
|
||
: {};
|
||
migrationMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`tables=${data.table_count || 0}`,
|
||
`file=${data.file_created ? 'created' : 'preview'}`,
|
||
`executed=${data.migration_executed ? 'yes' : 'no'}`,
|
||
`additive=${data.safety_checks && data.safety_checks.forward_sql_additive_only ? 'yes' : 'no'}`,
|
||
`seed_script=${seedWriter.script_created ? 'created' : 'missing'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderMigrationBody = data => {
|
||
const operations = (data.table_operations || []).slice(0, 8);
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const commands = data.command_plan || {};
|
||
const migrationCommand = commands.migration_apply_command || {};
|
||
const seedCommand = commands.seed_writer_command || {};
|
||
migrationBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">
|
||
建議檔名:${escapeHtml(data.suggested_filename || '')}。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}
|
||
</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-migration-tables>
|
||
<p class="market-intel-deploy-section-title">TABLE DRAFT</p>
|
||
<div class="market-intel-operation-list">${
|
||
operations.map(item => `
|
||
<article class="market-intel-operation">
|
||
<strong>${escapeHtml(item.table)}</strong>
|
||
<small>${escapeHtml(item.operation)} / ${escapeHtml(item.write_status)}</small>
|
||
</article>
|
||
`).join('')
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-migration-commands>
|
||
<p class="market-intel-deploy-section-title">COMMAND DESIGN</p>
|
||
<div class="market-intel-check-list">
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>migration_apply_command</strong>
|
||
<small>${escapeHtml(migrationCommand.command || '')}</small>
|
||
</div>
|
||
<span>${migrationCommand.executed ? 'EXECUTED' : 'BLOCKED'}</span>
|
||
</div>
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>seed_writer_command</strong>
|
||
<small>${escapeHtml(seedCommand.command || '')}</small>
|
||
<small>${escapeHtml(seedCommand.notes || '')}</small>
|
||
</div>
|
||
<span>${seedCommand.executed ? 'EXECUTED' : seedCommand.script_created ? 'SCRIPT' : 'DESIGN'}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadMigration = async () => {
|
||
if (!migrationMeta || !migrationBody) return;
|
||
migrationBody.innerHTML = '<div class="market-intel-empty">讀取 migration 草案中...</div>';
|
||
try {
|
||
const response = await fetch(migrationEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderMigrationMeta(data);
|
||
renderMigrationBody(data);
|
||
} catch (error) {
|
||
migrationMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
migrationBody.innerHTML = `<div class="market-intel-empty">migration 草案讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderMigrationDrillMeta = data => {
|
||
migrationDrillMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`schema=${data.schema_state || 'unknown'}`,
|
||
`probe=${data.read_only_query_executed ? 'read-only' : 'planned'}`,
|
||
`review=${data.drill_ready_for_operator_review ? 'ready' : 'blocked'}`,
|
||
`apply=${data.ready_to_apply_migration ? 'yes' : 'manual'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderMigrationDrillBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const checks = Object.entries(data.checks || {});
|
||
const preApply = data.pre_apply_checklist || [];
|
||
const postApply = data.post_apply_verification || [];
|
||
const rollback = data.rollback_drill || {};
|
||
const risks = data.risk_register || [];
|
||
const commands = data.manual_commands || {};
|
||
const schema = data.schema_db_probe_summary || {};
|
||
const seedDiff = data.platform_seed_db_diff_summary || {};
|
||
const renderStep = item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key || item.label || 'step')}</strong>
|
||
<small>${escapeHtml(item.label || item.key || '')}</small>
|
||
</div>
|
||
<span>${escapeHtml(item.status || 'required').toUpperCase()}</span>
|
||
</div>
|
||
`;
|
||
migrationDrillBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此演練只集中正式 migration 前的只讀探測、人工套用清單與回滾演練;API 不執行 psql、不寫 DB、不跑 rollback、不重啟容器。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-empty mb-3">schema_missing=${escapeHtml((schema.missing_tables || []).length)} / seed_missing=${escapeHtml((seedDiff.missing_codes || []).length)} / command=${escapeHtml(commands.migration_apply || '')}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-migration-drill-checks>
|
||
<p class="market-intel-deploy-section-title">DRILL CHECKS</p>
|
||
<div class="market-intel-check-list">${
|
||
checks.length
|
||
? checks.map(([name, passed]) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(name)}</strong>
|
||
</div>
|
||
<span>${passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('')
|
||
: '<div class="market-intel-empty">尚未提供 drill check。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-migration-drill-preapply>
|
||
<p class="market-intel-deploy-section-title">PRE-APPLY</p>
|
||
<div class="market-intel-check-list">${
|
||
preApply.length
|
||
? preApply.map(renderStep).join('')
|
||
: '<div class="market-intel-empty">尚未提供套用前清單。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-migration-drill-postapply>
|
||
<p class="market-intel-deploy-section-title">POST-APPLY</p>
|
||
<div class="market-intel-check-list">${
|
||
postApply.length
|
||
? postApply.map(renderStep).join('')
|
||
: '<div class="market-intel-empty">尚未提供套用後驗證。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-migration-drill-rollback>
|
||
<p class="market-intel-deploy-section-title">ROLLBACK DRILL</p>
|
||
<div class="market-intel-check-list">
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(rollback.mode || 'rollback_drill')}</strong>
|
||
<small>${escapeHtml(rollback.manual_command_shape || '')}</small>
|
||
</div>
|
||
<span>${rollback.rollback_executed ? 'EXECUTED' : 'MANUAL'}</span>
|
||
</div>
|
||
${(rollback.fallback_first || []).map((item, index) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(`fallback_${index + 1}`)}</strong>
|
||
<small>${escapeHtml(item)}</small>
|
||
</div>
|
||
<span>FIRST</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-migration-drill-risks>
|
||
<p class="market-intel-deploy-section-title">RISK REGISTER</p>
|
||
<div class="market-intel-check-list">${
|
||
risks.length
|
||
? risks.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${escapeHtml(item.severity || 'medium').toUpperCase()}</span>
|
||
</div>
|
||
`).join('')
|
||
: '<div class="market-intel-empty">尚未提供風險清單。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadMigrationDrill = async () => {
|
||
if (!migrationDrillMeta || !migrationDrillBody) return;
|
||
migrationDrillBody.innerHTML = '<div class="market-intel-empty">讀取 migration 套用演練中...</div>';
|
||
try {
|
||
const response = await fetch(migrationDrillEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderMigrationDrillMeta(data);
|
||
renderMigrationDrillBody(data);
|
||
} catch (error) {
|
||
migrationDrillMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
migrationDrillBody.innerHTML = `<div class="market-intel-empty">migration 套用演練讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCatalogReviewMeta = data => {
|
||
catalogReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`catalog=${data.catalog_state || 'unknown'}`,
|
||
`risk=${data.risk_level || 'info'}`,
|
||
`tables=${data.table_catalog ? `${data.table_catalog.existing_count || 0}/${data.table_catalog.expected_count || 0}` : '0/0'}`,
|
||
`apply=${data.apply_path || 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderCatalogReviewBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const checks = Object.entries(data.safe_checks || {});
|
||
const tableCatalog = data.table_catalog || {};
|
||
const seedCatalog = data.seed_catalog || {};
|
||
const findings = data.findings || [];
|
||
const nextSteps = data.operator_next_steps || [];
|
||
const probeTargets = data.manual_probe_targets || [];
|
||
const renderCheck = ([name, passed]) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(name)}</strong>
|
||
</div>
|
||
<span>${passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`;
|
||
const renderNamedItem = item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key || item.label || 'item')}</strong>
|
||
<small>${escapeHtml(item.label || item.key || '')}</small>
|
||
</div>
|
||
<span>${escapeHtml(item.status || item.severity || 'review').toUpperCase()}</span>
|
||
</div>
|
||
`;
|
||
catalogReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只判讀正式 DB catalog 與 platform seed diff 的只讀結果;API 不執行 migration、不寫 DB、不跑 rollback、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-empty mb-3">tables=${escapeHtml(tableCatalog.existing_count || 0)}/${escapeHtml(tableCatalog.expected_count || 0)} / missing=${escapeHtml(tableCatalog.missing_count || 0)} / seed_missing=${escapeHtml((seedCatalog.missing_codes || []).length)} / seed_changed=${escapeHtml((seedCatalog.changed_codes || []).length)}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-catalog-review-checks>
|
||
<p class="market-intel-deploy-section-title">SAFE CHECKS</p>
|
||
<div class="market-intel-check-list">${
|
||
checks.length
|
||
? checks.map(renderCheck).join('')
|
||
: '<div class="market-intel-empty">尚未提供 safety checks。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-catalog-review-findings>
|
||
<p class="market-intel-deploy-section-title">CATALOG FINDINGS</p>
|
||
<div class="market-intel-check-list">${
|
||
findings.length
|
||
? findings.map(renderNamedItem).join('')
|
||
: '<div class="market-intel-empty">尚未提供 catalog finding。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-catalog-review-tables>
|
||
<p class="market-intel-deploy-section-title">TABLE STATUS</p>
|
||
<div class="market-intel-check-list">${
|
||
(tableCatalog.table_statuses || []).slice(0, 8).map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.table)}</strong>
|
||
</div>
|
||
<span>${item.exists ? 'EXISTS' : 'MISSING'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 table status。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-catalog-review-next>
|
||
<p class="market-intel-deploy-section-title">NEXT STEPS</p>
|
||
<div class="market-intel-check-list">${
|
||
nextSteps.length
|
||
? nextSteps.map(renderNamedItem).join('')
|
||
: '<div class="market-intel-empty">尚未提供下一步。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-catalog-review-probes>
|
||
<p class="market-intel-deploy-section-title">READ-ONLY PROBES</p>
|
||
<div class="market-intel-check-list">${
|
||
probeTargets.length
|
||
? probeTargets.map(item => renderNamedItem({ key: item, label: item, status: 'manual' })).join('')
|
||
: '<div class="market-intel-empty">尚未提供 probe targets。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCatalogReview = async () => {
|
||
if (!catalogReviewMeta || !catalogReviewBody) return;
|
||
catalogReviewBody.innerHTML = '<div class="market-intel-empty">讀取正式 DB catalog 判讀中...</div>';
|
||
try {
|
||
const response = await fetch(catalogReviewEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderCatalogReviewMeta(data);
|
||
renderCatalogReviewBody(data);
|
||
} catch (error) {
|
||
catalogReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
catalogReviewBody.innerHTML = `<div class="market-intel-empty">正式 DB catalog 判讀讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderLiveSmokeMeta = data => {
|
||
liveSmokeMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`result=${data.smoke_result || 'planned'}`,
|
||
`catalog=${data.catalog_state || 'unknown'}`,
|
||
`passed=${data.live_smoke_passed ? 'yes' : 'no'}`,
|
||
`writes=${data.database_write_executed ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderLiveSmokeBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const checks = Object.entries(data.safety_checks || {});
|
||
const summary = data.catalog_review_summary || {};
|
||
const tableCatalog = summary.table_catalog || {};
|
||
const seedCatalog = summary.seed_catalog || {};
|
||
const findings = summary.findings || [];
|
||
const steps = data.operator_next_steps || [];
|
||
const targets = data.manual_probe_targets || [];
|
||
const renderCheck = ([name, passed]) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(name)}</strong>
|
||
</div>
|
||
<span>${passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`;
|
||
const renderNamedItem = item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key || item.label || 'item')}</strong>
|
||
<small>${escapeHtml(item.label || item.key || '')}</small>
|
||
</div>
|
||
<span>${escapeHtml(item.status || item.severity || 'manual').toUpperCase()}</span>
|
||
</div>
|
||
`;
|
||
liveSmokeBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡預設不跑正式 DB 探測;操作員手動啟動只讀 smoke 模式時,只讀查 catalog / seed diff,不執行 migration、不寫 DB、不跑 rollback。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-empty mb-3">tables=${escapeHtml(tableCatalog.existing_count || 0)}/${escapeHtml(tableCatalog.expected_count || 0)} / seed_missing=${escapeHtml((seedCatalog.missing_codes || []).length)} / seed_changed=${escapeHtml((seedCatalog.changed_codes || []).length)} / tolerated_seed_error=${data.seed_probe_error_tolerated ? 'yes' : 'no'}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-live-smoke-checks>
|
||
<p class="market-intel-deploy-section-title">SMOKE CHECKS</p>
|
||
<div class="market-intel-check-list">${
|
||
checks.length
|
||
? checks.map(renderCheck).join('')
|
||
: '<div class="market-intel-empty">尚未提供 smoke checks。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-live-smoke-findings>
|
||
<p class="market-intel-deploy-section-title">FINDINGS</p>
|
||
<div class="market-intel-check-list">${
|
||
findings.length
|
||
? findings.map(renderNamedItem).join('')
|
||
: '<div class="market-intel-empty">尚未提供 findings。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-live-smoke-next>
|
||
<p class="market-intel-deploy-section-title">NEXT STEPS</p>
|
||
<div class="market-intel-check-list">${
|
||
steps.length
|
||
? steps.map(renderNamedItem).join('')
|
||
: '<div class="market-intel-empty">尚未提供下一步。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-live-smoke-targets>
|
||
<p class="market-intel-deploy-section-title">MANUAL TARGETS</p>
|
||
<div class="market-intel-check-list">${
|
||
targets.length
|
||
? targets.map(item => renderNamedItem({ key: item, label: item, status: 'manual' })).join('')
|
||
: '<div class="market-intel-empty">尚未提供 targets。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadLiveSmoke = async () => {
|
||
if (!liveSmokeMeta || !liveSmokeBody) return;
|
||
liveSmokeBody.innerHTML = '<div class="market-intel-empty">讀取正式 DB 只讀 smoke 中...</div>';
|
||
try {
|
||
const response = await fetch(liveSmokeEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderLiveSmokeMeta(data);
|
||
renderLiveSmokeBody(data);
|
||
} catch (error) {
|
||
liveSmokeMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
liveSmokeBody.innerHTML = `<div class="market-intel-empty">正式 DB 只讀 smoke 讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderLiveInventoryMeta = data => {
|
||
liveInventoryMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`execute=${data.execute_requested ? 'true' : 'false'}`,
|
||
`query=${data.read_only_query_executed ? 'yes' : 'no'}`,
|
||
`tables=${(data.existing_tables || []).length}/${(data.expected_tables || []).length}`,
|
||
`rows=${data.total_rows || 0}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderLiveInventoryBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const checks = Object.entries(data.safety_checks || {});
|
||
const tables = data.table_statuses || [];
|
||
const platformRows = data.platform_breakdown || [];
|
||
const campaignRows = data.campaign_status_breakdown || [];
|
||
const productRows = data.product_activity_summary || [];
|
||
const matchRows = data.match_status_breakdown || [];
|
||
const alertRows = data.alert_review_state_breakdown || [];
|
||
const crawlerRows = data.crawler_run_summary || [];
|
||
const renderCheck = ([name, passed]) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(name)}</strong>
|
||
</div>
|
||
<span>${passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`;
|
||
const renderTable = item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.table)}</strong>
|
||
</div>
|
||
<span>${item.row_count == null ? escapeHtml(item.status || 'planned').toUpperCase() : escapeHtml(item.row_count)}</span>
|
||
</div>
|
||
`;
|
||
const renderRow = (label, detail, status) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(label)}</strong>
|
||
<small>${escapeHtml(detail)}</small>
|
||
</div>
|
||
<span>${escapeHtml(status)}</span>
|
||
</div>
|
||
`;
|
||
liveInventoryBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡預設只顯示 planned;操作員手動啟動只讀庫存模式時,只查 market_* count / group by,不寫 DB、不跑 migration、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-empty mb-3">summary_ready=${data.summary_ready ? 'yes' : 'no'} / missing=${escapeHtml((data.missing_tables || []).length)} / total_rows=${escapeHtml(data.total_rows || 0)}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-live-inventory-checks>
|
||
<p class="market-intel-deploy-section-title">SAFETY CHECKS</p>
|
||
<div class="market-intel-check-list">${
|
||
checks.length
|
||
? checks.map(renderCheck).join('')
|
||
: '<div class="market-intel-empty">尚未提供 safety checks。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-live-inventory-tables>
|
||
<p class="market-intel-deploy-section-title">TABLE COUNTS</p>
|
||
<div class="market-intel-check-list">${
|
||
tables.length
|
||
? tables.map(renderTable).join('')
|
||
: '<div class="market-intel-empty">尚未提供 table count。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-live-inventory-platforms>
|
||
<p class="market-intel-deploy-section-title">PLATFORMS</p>
|
||
<div class="market-intel-check-list">${
|
||
platformRows.length
|
||
? platformRows.map(item => renderRow(item.code, item.name || '', item.enabled ? 'ON' : 'OFF')).join('')
|
||
: '<div class="market-intel-empty">尚未載入平台資料。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-live-inventory-campaigns>
|
||
<p class="market-intel-deploy-section-title">CAMPAIGNS</p>
|
||
<div class="market-intel-check-list">${
|
||
campaignRows.length
|
||
? campaignRows.map(item => renderRow(item.platform_code, item.status || 'unknown', item.campaign_count || 0)).join('')
|
||
: '<div class="market-intel-empty">尚未有活動資料。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-live-inventory-products>
|
||
<p class="market-intel-deploy-section-title">PRODUCTS</p>
|
||
<div class="market-intel-check-list">${
|
||
productRows.length
|
||
? productRows.map(item => renderRow(item.platform_code, `active=${item.is_active ? 'yes' : 'no'} / latest=${item.latest_seen_at || 'n/a'}`, item.product_count || 0)).join('')
|
||
: '<div class="market-intel-empty">尚未有活動商品資料。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-live-inventory-matches>
|
||
<p class="market-intel-deploy-section-title">MATCHES</p>
|
||
<div class="market-intel-check-list">${
|
||
matchRows.length
|
||
? matchRows.map(item => renderRow(item.match_status || 'unknown', `avg=${item.avg_match_score || 0}`, item.match_count || 0)).join('')
|
||
: '<div class="market-intel-empty">尚未有商品比對資料。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-live-inventory-alerts>
|
||
<p class="market-intel-deploy-section-title">ALERT QUEUE</p>
|
||
<div class="market-intel-check-list">${
|
||
alertRows.length
|
||
? alertRows.map(item => renderRow(item.review_state || 'unknown', item.priority_lane || 'watch', item.alert_count || 0)).join('')
|
||
: '<div class="market-intel-empty">尚未有告警審核佇列資料。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-live-inventory-crawler>
|
||
<p class="market-intel-deploy-section-title">CRAWLER RUNS</p>
|
||
<div class="market-intel-check-list">${
|
||
crawlerRows.length
|
||
? crawlerRows.map(item => renderRow(item.platform_code || 'all', `${item.status || 'unknown'} / dry=${item.dry_run ? 'yes' : 'no'}`, item.run_count || 0)).join('')
|
||
: '<div class="market-intel-empty">尚未有 crawler run 資料。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadLiveInventory = async () => {
|
||
if (!liveInventoryMeta || !liveInventoryBody) return;
|
||
liveInventoryBody.innerHTML = '<div class="market-intel-empty">讀取正式 DB 庫存總覽中...</div>';
|
||
try {
|
||
const response = await fetch(liveInventoryEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderLiveInventoryMeta(data);
|
||
renderLiveInventoryBody(data);
|
||
} catch (error) {
|
||
liveInventoryMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
liveInventoryBody.innerHTML = `<div class="market-intel-empty">正式 DB 庫存總覽讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderApprovalMeta = data => {
|
||
approvalMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`ready=${data.ready_for_real_write ? 'yes' : 'no'}`,
|
||
`gates=${(data.approval_gates || []).length}`,
|
||
`blocked=${(data.blocked_reasons || []).length}`,
|
||
`db_commit=${data.database_commit_executed ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderApprovalBody = data => {
|
||
const gates = data.approval_gates || [];
|
||
const sequence = (data.operator_sequence || []).slice(0, 6);
|
||
const rollback = data.rollback_plan || [];
|
||
const renderNamedItem = (item, status) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key || item.label || 'item')}</strong>
|
||
<small>${escapeHtml(item.label || item.key || '')}</small>
|
||
</div>
|
||
<span>${escapeHtml(status).toUpperCase()}</span>
|
||
</div>
|
||
`;
|
||
|
||
approvalBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">正式寫入尚未批准;目前沒有 DB session、沒有 commit、沒有 scheduler attach。</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-approval-gates>
|
||
<p class="market-intel-deploy-section-title">APPROVAL GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => renderNamedItem(item, item.passed ? 'pass' : 'block')).join('')
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-approval-sequence>
|
||
<p class="market-intel-deploy-section-title">OPERATOR SEQUENCE</p>
|
||
<div class="market-intel-check-list">${
|
||
sequence.map(item => renderNamedItem(item, 'pending')).join('')
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-approval-rollback>
|
||
<p class="market-intel-deploy-section-title">ROLLBACK</p>
|
||
<div class="market-intel-check-list">${
|
||
rollback.map(item => renderNamedItem(item, 'ready')).join('')
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadApproval = async () => {
|
||
if (!approvalMeta || !approvalBody) return;
|
||
approvalBody.innerHTML = '<div class="market-intel-empty">讀取批准檢查中...</div>';
|
||
try {
|
||
const response = await fetch(approvalEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderApprovalMeta(data);
|
||
renderApprovalBody(data);
|
||
} catch (error) {
|
||
approvalMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
approvalBody.innerHTML = `<div class="market-intel-empty">批准檢查讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderDeployMeta = data => {
|
||
deployMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`deployed=${data.production_deployed ? 'yes' : 'no'}`,
|
||
`commit=${data.git_committed ? 'yes' : 'no'}`,
|
||
`push=${data.git_pushed ? 'yes' : 'no'}`,
|
||
`ready=${data.ready_for_production_deploy ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderDeployBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const checks = Object.entries(data.checks || {});
|
||
const steps = data.required_manual_steps || [];
|
||
const fallback = data.fallback_plan || [];
|
||
const boundaries = data.safe_deploy_boundaries || [];
|
||
const smokeTargets = data.production_smoke_targets || [];
|
||
const renderItem = (item, fallbackStatus) => {
|
||
const normalized = typeof item === 'string' ? { key: item, label: item } : (item || {});
|
||
const status = normalized.status || fallbackStatus || 'required';
|
||
return `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(normalized.key || normalized.label || 'item')}</strong>
|
||
<small>${escapeHtml(normalized.label || normalized.key || '')}</small>
|
||
${normalized.trigger ? `<small>trigger=${escapeHtml(normalized.trigger)}</small>` : ''}
|
||
</div>
|
||
<span>${escapeHtml(status).toUpperCase()}</span>
|
||
</div>
|
||
`;
|
||
};
|
||
deployBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">API 不執行推版;需由操作員依 app-only SOP 完成備份、同步、重啟與正式 smoke。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-check-list">${
|
||
checks.map(([name, passed]) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(name)}</strong>
|
||
</div>
|
||
<span>${passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('')
|
||
}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-deploy-steps>
|
||
<p class="market-intel-deploy-section-title">MANUAL STEPS</p>
|
||
<div class="market-intel-check-list">${
|
||
steps.length
|
||
? steps.map(item => renderItem(item, 'pending')).join('')
|
||
: '<div class="market-intel-empty">尚未提供人工步驟。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-deploy-fallback>
|
||
<p class="market-intel-deploy-section-title">備援方案</p>
|
||
<div class="market-intel-check-list">${
|
||
fallback.length
|
||
? fallback.map(item => renderItem(item, 'ready')).join('')
|
||
: '<div class="market-intel-empty">尚未提供備援方案。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-deploy-boundaries>
|
||
<p class="market-intel-deploy-section-title">DEPLOY BOUNDARIES</p>
|
||
<div class="market-intel-check-list">${
|
||
boundaries.length
|
||
? boundaries.map(item => renderItem(item, 'required')).join('')
|
||
: '<div class="market-intel-empty">尚未提供部署邊界。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-deploy-smoke>
|
||
<p class="market-intel-deploy-section-title">SMOKE TARGETS</p>
|
||
<div class="market-intel-check-list">${
|
||
smokeTargets.length
|
||
? smokeTargets.map(item => renderItem(String(item), 'required')).join('')
|
||
: '<div class="market-intel-empty">尚未提供 smoke 目標。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadDeploy = async () => {
|
||
if (!deployMeta || !deployBody) return;
|
||
deployBody.innerHTML = '<div class="market-intel-empty">讀取推版準備中...</div>';
|
||
try {
|
||
const response = await fetch(deployEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderDeployMeta(data);
|
||
renderDeployBody(data);
|
||
} catch (error) {
|
||
deployMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
deployBody.innerHTML = `<div class="market-intel-empty">推版準備讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
if (refresh) {
|
||
refresh.addEventListener('click', loadPreview);
|
||
}
|
||
if (writerRefresh) {
|
||
writerRefresh.addEventListener('click', loadWriter);
|
||
}
|
||
if (cliRefresh) {
|
||
cliRefresh.addEventListener('click', loadCli);
|
||
}
|
||
if (dbProbeRefresh) {
|
||
dbProbeRefresh.addEventListener('click', loadDbProbe);
|
||
}
|
||
if (seedDiffRefresh) {
|
||
seedDiffRefresh.addEventListener('click', loadSeedDiff);
|
||
}
|
||
if (legacyBridgeRefresh) {
|
||
legacyBridgeRefresh.addEventListener('click', loadLegacyBridge);
|
||
}
|
||
if (mcpReadinessRefresh) {
|
||
mcpReadinessRefresh.addEventListener('click', loadMcpReadiness);
|
||
}
|
||
if (mcpPreflightRefresh) {
|
||
mcpPreflightRefresh.addEventListener('click', loadMcpPreflight);
|
||
}
|
||
if (mcpActivationRefresh) {
|
||
mcpActivationRefresh.addEventListener('click', loadMcpActivation);
|
||
}
|
||
if (mcpFetchGateRefresh) {
|
||
mcpFetchGateRefresh.addEventListener('click', loadMcpFetchGate);
|
||
}
|
||
if (manualSampleRefresh) {
|
||
manualSampleRefresh.addEventListener('click', loadManualSample);
|
||
}
|
||
if (sampleAcceptanceRefresh) {
|
||
sampleAcceptanceRefresh.addEventListener('click', loadSampleAcceptance);
|
||
}
|
||
if (sampleReviewRefresh) {
|
||
sampleReviewRefresh.addEventListener('click', loadSampleReview);
|
||
}
|
||
if (sampleReviewEvaluate) {
|
||
sampleReviewEvaluate.addEventListener('click', evaluateSampleReview);
|
||
}
|
||
if (sampleCandidateHandoff) {
|
||
sampleCandidateHandoff.addEventListener('click', loadCandidateHandoff);
|
||
}
|
||
if (sampleCandidateQueueDraft) {
|
||
sampleCandidateQueueDraft.addEventListener('click', loadCandidateQueueDraft);
|
||
}
|
||
if (sampleCandidateQueueApproval) {
|
||
sampleCandidateQueueApproval.addEventListener('click', loadCandidateQueueApproval);
|
||
}
|
||
if (sampleCandidateQueueTransaction) {
|
||
sampleCandidateQueueTransaction.addEventListener('click', loadCandidateQueueTransaction);
|
||
}
|
||
if (sampleCandidateQueueWriter) {
|
||
sampleCandidateQueueWriter.addEventListener('click', loadCandidateQueueWriter);
|
||
}
|
||
if (sampleCandidateQueuePreflight) {
|
||
sampleCandidateQueuePreflight.addEventListener('click', loadCandidateQueuePreflight);
|
||
}
|
||
if (sampleCandidateQueuePostwriteSmoke) {
|
||
sampleCandidateQueuePostwriteSmoke.addEventListener('click', loadCandidateQueuePostwriteSmoke);
|
||
}
|
||
if (sampleCandidateQueueOperatorDrill) {
|
||
sampleCandidateQueueOperatorDrill.addEventListener('click', loadCandidateQueueOperatorDrill);
|
||
}
|
||
if (sampleCandidateQueueRunPackage) {
|
||
sampleCandidateQueueRunPackage.addEventListener('click', loadCandidateQueueRunPackage);
|
||
}
|
||
if (sampleCandidateQueueRunReadiness) {
|
||
sampleCandidateQueueRunReadiness.addEventListener('click', loadCandidateQueueRunReadiness);
|
||
}
|
||
if (sampleCandidateQueueRunReceipt) {
|
||
sampleCandidateQueueRunReceipt.addEventListener('click', loadCandidateQueueRunReceipt);
|
||
}
|
||
if (sampleCandidateQueueRunCloseout) {
|
||
sampleCandidateQueueRunCloseout.addEventListener('click', loadCandidateQueueRunCloseout);
|
||
}
|
||
if (sampleCandidateQueueReviewHandoff) {
|
||
sampleCandidateQueueReviewHandoff.addEventListener('click', loadCandidateQueueReviewHandoff);
|
||
}
|
||
if (sampleCandidateQueueReviewInventory) {
|
||
sampleCandidateQueueReviewInventory.addEventListener('click', loadCandidateQueueReviewInventory);
|
||
}
|
||
if (sampleCandidateQueueReviewDecision) {
|
||
sampleCandidateQueueReviewDecision.addEventListener('click', loadCandidateQueueReviewDecision);
|
||
}
|
||
if (sampleCandidateQueueReviewDecisionApproval) {
|
||
sampleCandidateQueueReviewDecisionApproval.addEventListener('click', loadCandidateQueueReviewDecisionApproval);
|
||
}
|
||
if (sampleCandidateQueueReviewDecisionTransaction) {
|
||
sampleCandidateQueueReviewDecisionTransaction.addEventListener('click', loadCandidateQueueReviewDecisionTransaction);
|
||
}
|
||
if (sampleCandidateQueueReviewDecisionPreflight) {
|
||
sampleCandidateQueueReviewDecisionPreflight.addEventListener('click', loadCandidateQueueReviewDecisionPreflight);
|
||
}
|
||
if (sampleCandidateQueueReviewDecisionPostwriteSmoke) {
|
||
sampleCandidateQueueReviewDecisionPostwriteSmoke.addEventListener('click', loadCandidateQueueReviewDecisionPostwriteSmoke);
|
||
}
|
||
if (sampleCandidateQueueReviewDecisionOperatorDrill) {
|
||
sampleCandidateQueueReviewDecisionOperatorDrill.addEventListener('click', loadCandidateQueueReviewDecisionOperatorDrill);
|
||
}
|
||
if (sampleCandidateQueueReviewDecisionRunPackage) {
|
||
sampleCandidateQueueReviewDecisionRunPackage.addEventListener('click', loadCandidateQueueReviewDecisionRunPackage);
|
||
}
|
||
if (sampleCandidateQueueReviewDecisionRunReadiness) {
|
||
sampleCandidateQueueReviewDecisionRunReadiness.addEventListener('click', loadCandidateQueueReviewDecisionRunReadiness);
|
||
}
|
||
if (sampleCandidateQueueReviewDecisionWriter) {
|
||
sampleCandidateQueueReviewDecisionWriter.addEventListener('click', loadCandidateQueueReviewDecisionWriter);
|
||
}
|
||
if (sampleCandidateQueueReviewDecisionRunReceipt) {
|
||
sampleCandidateQueueReviewDecisionRunReceipt.addEventListener('click', loadCandidateQueueReviewDecisionRunReceipt);
|
||
}
|
||
if (sampleCandidateQueueReviewDecisionRunCloseout) {
|
||
sampleCandidateQueueReviewDecisionRunCloseout.addEventListener('click', loadCandidateQueueReviewDecisionRunCloseout);
|
||
}
|
||
if (sampleCandidateQueueReviewDecisionPostCloseoutInventory) {
|
||
sampleCandidateQueueReviewDecisionPostCloseoutInventory.addEventListener('click', loadCandidateQueueReviewDecisionPostCloseoutInventory);
|
||
}
|
||
if (sampleCandidateQueueReviewCompletionArchive) {
|
||
sampleCandidateQueueReviewCompletionArchive.addEventListener('click', loadCandidateQueueReviewCompletionArchive);
|
||
}
|
||
if (sampleCandidateQueueReviewArchiveSummary) {
|
||
sampleCandidateQueueReviewArchiveSummary.addEventListener('click', loadCandidateQueueReviewArchiveSummary);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPreflight) {
|
||
sampleCandidateQueueReviewAiSummaryPreflight.addEventListener('click', loadCandidateQueueReviewAiSummaryPreflight);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryRunPackage) {
|
||
sampleCandidateQueueReviewAiSummaryRunPackage.addEventListener('click', loadCandidateQueueReviewAiSummaryRunPackage);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryOutputReceipt) {
|
||
sampleCandidateQueueReviewAiSummaryOutputReceipt.addEventListener('click', loadCandidateQueueReviewAiSummaryOutputReceipt);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistencePreflight) {
|
||
sampleCandidateQueueReviewAiSummaryPersistencePreflight.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistencePreflight);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTransaction) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTransaction.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTransaction);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceWriterPreflight) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceWriterPreflight.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceWriterPreflight);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceRunPackage) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceRunPackage.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceRunPackage);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceRunReadiness) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceRunReadiness.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceRunReadiness);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceRunReceipt) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceRunReceipt.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceRunReceipt);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceRunCloseout) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceRunCloseout.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceRunCloseout);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchGate) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchGate.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchGate);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunPackage) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunPackage.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunPackage);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunReadiness) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunReadiness.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunReadiness);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunReceipt) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunReceipt.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunReceipt);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchCloseout) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchCloseout.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchCloseout);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchArchive) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchArchive.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchArchive);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchArchiveSummary) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchArchiveSummary.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchArchiveSummary);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportInput) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportInput.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportInput);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunPackage) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunPackage.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunPackage);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunReadiness) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunReadiness.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunReadiness);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunReceipt) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunReceipt.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunReceipt);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCloseout) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCloseout.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCloseout);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportArchive) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportArchive.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportArchive);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportArchiveSummary) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportArchiveSummary.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportArchiveSummary);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogHandoff) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogHandoff.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogHandoff);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogIndex) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogIndex.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogIndex);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogWritePreflight) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogWritePreflight.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogWritePreflight);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordWrite) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordWrite.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordWrite);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunPackage) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunPackage.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunPackage);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunReadiness) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunReadiness.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunReadiness);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunReceipt) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunReceipt.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunReceipt);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordCommit) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordCommit.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordCommit);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordCloseout) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordCloseout.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordCloseout);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordArchive) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordArchive.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordArchive);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordArchiveSummary) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordArchiveSummary.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordArchiveSummary);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordFinalCloseout) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordFinalCloseout.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordFinalCloseout);
|
||
}
|
||
if (schedulerRefresh) {
|
||
schedulerRefresh.addEventListener('click', loadScheduler);
|
||
}
|
||
if (matchReviewRefresh) {
|
||
matchReviewRefresh.addEventListener('click', loadMatchReview);
|
||
}
|
||
if (opportunityRefresh) {
|
||
opportunityRefresh.addEventListener('click', loadOpportunity);
|
||
}
|
||
if (opportunityScoringRefresh) {
|
||
opportunityScoringRefresh.addEventListener('click', loadOpportunityScoring);
|
||
}
|
||
if (opportunityEvidenceRefresh) {
|
||
opportunityEvidenceRefresh.addEventListener('click', loadOpportunityEvidence);
|
||
}
|
||
if (opportunityAlertRefresh) {
|
||
opportunityAlertRefresh.addEventListener('click', loadOpportunityAlert);
|
||
}
|
||
if (migrationRefresh) {
|
||
migrationRefresh.addEventListener('click', loadMigration);
|
||
}
|
||
if (migrationDrillRefresh) {
|
||
migrationDrillRefresh.addEventListener('click', loadMigrationDrill);
|
||
}
|
||
if (catalogReviewRefresh) {
|
||
catalogReviewRefresh.addEventListener('click', loadCatalogReview);
|
||
}
|
||
if (liveSmokeRefresh) {
|
||
liveSmokeRefresh.addEventListener('click', loadLiveSmoke);
|
||
}
|
||
if (liveInventoryRefresh) {
|
||
liveInventoryRefresh.addEventListener('click', loadLiveInventory);
|
||
}
|
||
if (approvalRefresh) {
|
||
approvalRefresh.addEventListener('click', loadApproval);
|
||
}
|
||
if (deployRefresh) {
|
||
deployRefresh.addEventListener('click', loadDeploy);
|
||
}
|
||
loadPreview();
|
||
loadWriter();
|
||
loadCli();
|
||
loadDbProbe();
|
||
loadSeedDiff();
|
||
loadLegacyBridge();
|
||
loadMcpReadiness();
|
||
loadMcpPreflight();
|
||
loadMcpActivation();
|
||
loadMcpFetchGate();
|
||
loadManualSample();
|
||
loadSampleAcceptance();
|
||
loadSampleReview();
|
||
loadScheduler();
|
||
loadMatchReview();
|
||
loadOpportunity();
|
||
loadOpportunityScoring();
|
||
loadOpportunityEvidence();
|
||
loadOpportunityAlert();
|
||
loadMigration();
|
||
loadMigrationDrill();
|
||
loadCatalogReview();
|
||
loadLiveSmoke();
|
||
loadLiveInventory();
|
||
loadApproval();
|
||
loadDeploy();
|
||
})();
|
||
</script>
|
||
{% endblock %}
|