新增市場情報 Telegram dispatch report run readiness gate
All checks were successful
CD Pipeline / deploy (push) Successful in 1m6s

This commit is contained in:
OoO
2026-05-20 12:58:21 +08:00
parent 3cfc82925e
commit e4f3c8fc5e
10 changed files with 1309 additions and 96 deletions

View File

@@ -733,6 +733,9 @@
<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>
</div>
</div>
</div>
@@ -1105,6 +1108,7 @@
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 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') }}";
@@ -1154,6 +1158,7 @@
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 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;
@@ -7236,6 +7241,173 @@
}
};
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 readinessAPI/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 renderCandidateQueueReviewDecisionWriter = data => {
const blockers = (data.blocked_reasons || []).join(' / ');
const summary = data.statement_summary || {};
@@ -8984,6 +9156,9 @@
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunPackage) {
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunPackage.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunPackage);
}
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunReadiness) {
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunReadiness.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunReadiness);
}
if (schedulerRefresh) {
schedulerRefresh.addEventListener('click', loadScheduler);
}