補 OpenClaw QA 備援與市場情報交接
All checks were successful
CD Pipeline / deploy (push) Successful in 1m4s

This commit is contained in:
OoO
2026-05-19 12:46:43 +08:00
parent 8d0c442bdd
commit e7e045253d
16 changed files with 853 additions and 143 deletions

View File

@@ -628,6 +628,9 @@
<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>
</div>
</div>
</div>
@@ -965,6 +968,7 @@
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 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') }}";
@@ -979,6 +983,7 @@
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 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;
@@ -3006,6 +3011,124 @@
}
};
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 renderSchedulerMeta = data => {
schedulerMeta.innerHTML = [
`mode=${data.mode || 'unknown'}`,
@@ -4536,6 +4659,9 @@
if (sampleCandidateQueueRunCloseout) {
sampleCandidateQueueRunCloseout.addEventListener('click', loadCandidateQueueRunCloseout);
}
if (sampleCandidateQueueReviewHandoff) {
sampleCandidateQueueReviewHandoff.addEventListener('click', loadCandidateQueueReviewHandoff);
}
if (schedulerRefresh) {
schedulerRefresh.addEventListener('click', loadScheduler);
}