新增市場情報 AI summary Telegram dispatch archive
All checks were successful
CD Pipeline / deploy (push) Successful in 1m22s

This commit is contained in:
OoO
2026-05-20 11:40:13 +08:00
parent c2528c1339
commit 6bf05ec2f1
9 changed files with 1182 additions and 92 deletions

View File

@@ -721,6 +721,9 @@
<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>
</div>
</div>
</div>
@@ -1089,6 +1092,7 @@
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 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') }}";
@@ -1134,6 +1138,7 @@
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 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;
@@ -6631,6 +6636,132 @@
}
};
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 manifestAPI/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 renderCandidateQueueReviewDecisionWriter = data => {
const blockers = (data.blocked_reasons || []).join(' / ');
const summary = data.statement_summary || {};
@@ -8367,6 +8498,9 @@
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchCloseout) {
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchCloseout.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchCloseout);
}
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchArchive) {
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchArchive.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchArchive);
}
if (schedulerRefresh) {
schedulerRefresh.addEventListener('click', loadScheduler);
}