This commit is contained in:
@@ -631,6 +631,9 @@
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -969,6 +972,7 @@
|
||||
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 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') }}";
|
||||
@@ -984,6 +988,7 @@
|
||||
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 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;
|
||||
@@ -3129,6 +3134,121 @@
|
||||
}
|
||||
};
|
||||
|
||||
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 renderSchedulerMeta = data => {
|
||||
schedulerMeta.innerHTML = [
|
||||
`mode=${data.mode || 'unknown'}`,
|
||||
@@ -4662,6 +4782,9 @@
|
||||
if (sampleCandidateQueueReviewHandoff) {
|
||||
sampleCandidateQueueReviewHandoff.addEventListener('click', loadCandidateQueueReviewHandoff);
|
||||
}
|
||||
if (sampleCandidateQueueReviewInventory) {
|
||||
sampleCandidateQueueReviewInventory.addEventListener('click', loadCandidateQueueReviewInventory);
|
||||
}
|
||||
if (schedulerRefresh) {
|
||||
schedulerRefresh.addEventListener('click', loadScheduler);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user