This commit is contained in:
@@ -522,6 +522,24 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="market-intel-panel" data-market-intel-sample-review>
|
||||
<div class="market-intel-preview-head">
|
||||
<div>
|
||||
<p class="market-intel-muted momo-mono mb-1">MANUAL SAMPLE / REVIEW</p>
|
||||
<h2 class="market-intel-preview-title">樣本結果審核預覽</h2>
|
||||
</div>
|
||||
<button class="market-intel-icon-button" type="button" title="重新整理樣本結果審核預覽" data-market-intel-sample-review-refresh>
|
||||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="market-intel-preview-meta" data-market-intel-sample-review-meta>
|
||||
<span class="market-intel-pill">loading</span>
|
||||
</div>
|
||||
<div data-market-intel-sample-review-body>
|
||||
<div class="market-intel-empty">讀取樣本結果審核預覽中...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="market-intel-panel" data-market-intel-scheduler>
|
||||
<div class="market-intel-preview-head">
|
||||
<div>
|
||||
@@ -773,6 +791,7 @@
|
||||
const mcpFetchGateRoot = document.querySelector('[data-market-intel-mcp-fetch-gate]');
|
||||
const manualSampleRoot = document.querySelector('[data-market-intel-manual-sample]');
|
||||
const sampleAcceptanceRoot = document.querySelector('[data-market-intel-sample-acceptance]');
|
||||
const sampleReviewRoot = document.querySelector('[data-market-intel-sample-review]');
|
||||
const schedulerRoot = document.querySelector('[data-market-intel-scheduler]');
|
||||
const matchReviewRoot = document.querySelector('[data-market-intel-match-review]');
|
||||
const opportunityRoot = document.querySelector('[data-market-intel-opportunity]');
|
||||
@@ -786,7 +805,7 @@
|
||||
const liveInventoryRoot = document.querySelector('[data-market-intel-live-inventory]');
|
||||
const approvalRoot = document.querySelector('[data-market-intel-approval]');
|
||||
const deployRoot = document.querySelector('[data-market-intel-deploy]');
|
||||
if (!root && !writerRoot && !cliRoot && !dbProbeRoot && !seedDiffRoot && !legacyBridgeRoot && !mcpReadinessRoot && !mcpPreflightRoot && !mcpActivationRoot && !mcpFetchGateRoot && !manualSampleRoot && !sampleAcceptanceRoot && !schedulerRoot && !matchReviewRoot && !opportunityRoot && !opportunityScoringRoot && !opportunityEvidenceRoot && !opportunityAlertRoot && !migrationRoot && !migrationDrillRoot && !catalogReviewRoot && !liveSmokeRoot && !liveInventoryRoot && !approvalRoot && !deployRoot) return;
|
||||
if (!root && !writerRoot && !cliRoot && !dbProbeRoot && !seedDiffRoot && !legacyBridgeRoot && !mcpReadinessRoot && !mcpPreflightRoot && !mcpActivationRoot && !mcpFetchGateRoot && !manualSampleRoot && !sampleAcceptanceRoot && !sampleReviewRoot && !schedulerRoot && !matchReviewRoot && !opportunityRoot && !opportunityScoringRoot && !opportunityEvidenceRoot && !opportunityAlertRoot && !migrationRoot && !migrationDrillRoot && !catalogReviewRoot && !liveSmokeRoot && !liveInventoryRoot && !approvalRoot && !deployRoot) return;
|
||||
|
||||
const meta = root ? root.querySelector('[data-market-intel-preview-meta]') : null;
|
||||
const body = root ? root.querySelector('[data-market-intel-preview-body]') : null;
|
||||
@@ -836,6 +855,10 @@
|
||||
const sampleAcceptanceBody = sampleAcceptanceRoot ? sampleAcceptanceRoot.querySelector('[data-market-intel-sample-acceptance-body]') : null;
|
||||
const sampleAcceptanceRefresh = sampleAcceptanceRoot ? sampleAcceptanceRoot.querySelector('[data-market-intel-sample-acceptance-refresh]') : null;
|
||||
const sampleAcceptanceEndpoint = "{{ url_for('market_intel.market_intel_manual_sample_acceptance') }}";
|
||||
const sampleReviewMeta = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-review-meta]') : null;
|
||||
const sampleReviewBody = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-review-body]') : null;
|
||||
const sampleReviewRefresh = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-review-refresh]') : null;
|
||||
const sampleReviewEndpoint = "{{ url_for('market_intel.market_intel_manual_sample_review') }}";
|
||||
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;
|
||||
@@ -1809,6 +1832,99 @@
|
||||
}
|
||||
};
|
||||
|
||||
const renderSampleReviewMeta = data => {
|
||||
sampleReviewMeta.innerHTML = [
|
||||
`mode=${data.mode || 'unknown'}`,
|
||||
`loaded=${data.sample_result_loaded ? 'yes' : 'no'}`,
|
||||
`reviewed=${data.sample_result_reviewed ? 'yes' : 'no'}`,
|
||||
`result=${data.review_result || 'planned'}`,
|
||||
`import=${data.candidate_import_allowed ? 'open' : 'blocked'}`
|
||||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||||
};
|
||||
|
||||
const renderSampleReviewBody = data => {
|
||||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||||
const checks = data.review_checks || [];
|
||||
const findings = data.review_findings || [];
|
||||
const candidates = data.candidate_summary || {};
|
||||
const actions = data.operator_next_actions || [];
|
||||
const boundaries = data.safe_boundaries || [];
|
||||
const renderCheck = item => `
|
||||
<div class="market-intel-check">
|
||||
<div>
|
||||
<strong>${escapeHtml(item.key || 'check')}</strong>
|
||||
<small>${escapeHtml(item.label || '')}</small>
|
||||
</div>
|
||||
<span>${escapeHtml(item.status || 'planned').toUpperCase()}</span>
|
||||
</div>
|
||||
`;
|
||||
const renderNamedItem = (item, status) => {
|
||||
const normalized = typeof item === 'string' ? { key: item, label: item } : (item || {});
|
||||
return `
|
||||
<div class="market-intel-check">
|
||||
<div>
|
||||
<strong>${escapeHtml(normalized.key || normalized.label || 'item')}</strong>
|
||||
<small>${escapeHtml(normalized.label || normalized.key || '')}</small>
|
||||
</div>
|
||||
<span>${escapeHtml(normalized.severity || normalized.write_status || status || 'blocked').toUpperCase()}</span>
|
||||
</div>
|
||||
`;
|
||||
};
|
||||
sampleReviewBody.innerHTML = `
|
||||
<div class="market-intel-empty mb-3">此卡只用純函式審核人工 sample result;目前預設不載入結果、不抓外站、不存檔、不寫 DB、不建立候選活動。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||||
<div class="market-intel-empty mb-3">candidate_count=${escapeHtml(candidates.candidate_count || 0)} / accepted=${escapeHtml(candidates.accepted_candidate_count || 0)} / bands=${escapeHtml((candidates.accepted_candidate_bands || []).join('/'))}</div>
|
||||
<div class="market-intel-deploy-grid">
|
||||
<div data-market-intel-sample-review-checks>
|
||||
<p class="market-intel-deploy-section-title">REVIEW CHECKS</p>
|
||||
<div class="market-intel-check-list">${
|
||||
checks.length
|
||||
? checks.map(renderCheck).join('')
|
||||
: '<div class="market-intel-empty">尚未載入 sample result,審核檢查維持 planned。</div>'
|
||||
}</div>
|
||||
</div>
|
||||
<div data-market-intel-sample-review-findings>
|
||||
<p class="market-intel-deploy-section-title">FINDINGS</p>
|
||||
<div class="market-intel-check-list">${
|
||||
findings.length
|
||||
? findings.map(item => renderNamedItem(item, 'block')).join('')
|
||||
: '<div class="market-intel-empty">目前沒有審核發現;尚未載入結果時代表未評估。</div>'
|
||||
}</div>
|
||||
</div>
|
||||
<div data-market-intel-sample-review-actions>
|
||||
<p class="market-intel-deploy-section-title">NEXT ACTIONS</p>
|
||||
<div class="market-intel-check-list">${
|
||||
actions.length
|
||||
? actions.map(item => renderNamedItem(item, item.write_status || 'blocked')).join('')
|
||||
: '<div class="market-intel-empty">尚未提供下一步。</div>'
|
||||
}</div>
|
||||
</div>
|
||||
<div data-market-intel-sample-review-boundaries>
|
||||
<p class="market-intel-deploy-section-title">BOUNDARIES</p>
|
||||
<div class="market-intel-check-list">${
|
||||
boundaries.length
|
||||
? boundaries.map(item => renderNamedItem(String(item), 'required')).join('')
|
||||
: '<div class="market-intel-empty">尚未提供安全邊界。</div>'
|
||||
}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
};
|
||||
|
||||
const loadSampleReview = async () => {
|
||||
if (!sampleReviewMeta || !sampleReviewBody) return;
|
||||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">讀取樣本結果審核預覽中...</div>';
|
||||
try {
|
||||
const response = await fetch(sampleReviewEndpoint, { credentials: 'same-origin' });
|
||||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||
const data = await response.json();
|
||||
renderSampleReviewMeta(data);
|
||||
renderSampleReviewBody(data);
|
||||
} catch (error) {
|
||||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">樣本結果審核預覽讀取失敗:${escapeHtml(error.message)}</div>`;
|
||||
}
|
||||
};
|
||||
|
||||
const renderSchedulerMeta = data => {
|
||||
schedulerMeta.innerHTML = [
|
||||
`mode=${data.mode || 'unknown'}`,
|
||||
@@ -3297,6 +3413,9 @@
|
||||
if (sampleAcceptanceRefresh) {
|
||||
sampleAcceptanceRefresh.addEventListener('click', loadSampleAcceptance);
|
||||
}
|
||||
if (sampleReviewRefresh) {
|
||||
sampleReviewRefresh.addEventListener('click', loadSampleReview);
|
||||
}
|
||||
if (schedulerRefresh) {
|
||||
schedulerRefresh.addEventListener('click', loadScheduler);
|
||||
}
|
||||
@@ -3348,6 +3467,7 @@
|
||||
loadMcpFetchGate();
|
||||
loadManualSample();
|
||||
loadSampleAcceptance();
|
||||
loadSampleReview();
|
||||
loadScheduler();
|
||||
loadMatchReview();
|
||||
loadOpportunity();
|
||||
|
||||
Reference in New Issue
Block a user