新增市場情報佇列寫入後 smoke
All checks were successful
CD Pipeline / deploy (push) Successful in 1m3s

This commit is contained in:
OoO
2026-05-19 10:11:14 +08:00
parent c383a37f3f
commit e217865ccc
10 changed files with 640 additions and 50 deletions

View File

@@ -594,6 +594,9 @@
<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>
</div>
</div>
@@ -924,6 +927,7 @@
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 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') }}";
@@ -932,6 +936,7 @@
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 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;
@@ -2389,6 +2394,67 @@
}
};
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 renderSchedulerMeta = data => {
schedulerMeta.innerHTML = [
`mode=${data.mode || 'unknown'}`,
@@ -3901,6 +3967,9 @@
if (sampleCandidateQueuePreflight) {
sampleCandidateQueuePreflight.addEventListener('click', loadCandidateQueuePreflight);
}
if (sampleCandidateQueuePostwriteSmoke) {
sampleCandidateQueuePostwriteSmoke.addEventListener('click', loadCandidateQueuePostwriteSmoke);
}
if (schedulerRefresh) {
schedulerRefresh.addEventListener('click', loadScheduler);
}